From f85f8ebbfac946c19b3c6eb0f4170f579d0a4d25 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Wed, 4 Nov 2009 15:11:44 -0800 Subject: [PATCH] Initial implementation of OVSDB. --- Makefile.am | 1 + lib/automake.mk | 11 + lib/compiler.h | 1 + lib/ovsdb-data.c | 756 ++++++++++++++++++++++ lib/ovsdb-data.h | 128 ++++ lib/ovsdb-error.c | 221 +++++++ lib/ovsdb-error.h | 53 ++ lib/ovsdb-parser.c | 167 +++++ lib/ovsdb-parser.h | 74 +++ lib/ovsdb-types.c | 251 ++++++++ lib/ovsdb-types.h | 126 ++++ lib/sort.c | 70 ++ lib/sort.h | 26 + lib/vlog-modules.def | 5 + ovsdb/SPECS | 628 ++++++++++++++++++ ovsdb/automake.mk | 44 ++ ovsdb/column.c | 232 +++++++ ovsdb/column.h | 84 +++ ovsdb/condition.c | 284 +++++++++ ovsdb/condition.h | 72 +++ ovsdb/execution.c | 613 ++++++++++++++++++ ovsdb/file.c | 360 +++++++++++ ovsdb/file.h | 36 ++ ovsdb/jsonrpc-server.c | 362 +++++++++++ ovsdb/jsonrpc-server.h | 29 + ovsdb/ovsdb-server.c | 223 +++++++ ovsdb/ovsdb-tool.c | 204 ++++++ ovsdb/ovsdb.c | 262 ++++++++ ovsdb/ovsdb.h | 73 +++ ovsdb/query.c | 99 +++ ovsdb/query.h | 37 ++ ovsdb/row.c | 386 +++++++++++ ovsdb/row.h | 139 ++++ ovsdb/table.c | 228 +++++++ ovsdb/table.h | 63 ++ ovsdb/transaction.c | 444 +++++++++++++ ovsdb/transaction.h | 41 ++ ovsdb/trigger.c | 129 ++++ ovsdb/trigger.h | 44 ++ tests/automake.mk | 17 + tests/library.at | 1 + tests/ovsdb-column.at | 18 + tests/ovsdb-condition.at | 558 ++++++++++++++++ tests/ovsdb-data.at | 259 ++++++++ tests/ovsdb-execution.at | 321 ++++++++++ tests/ovsdb-file.at | 282 +++++++++ tests/ovsdb-query.at | 535 ++++++++++++++++ tests/ovsdb-row.at | 277 ++++++++ tests/ovsdb-table.at | 31 + tests/ovsdb-transaction.at | 384 +++++++++++ tests/ovsdb-trigger.at | 173 +++++ tests/ovsdb-types.at | 90 +++ tests/ovsdb.at | 35 + tests/test-ovsdb.c | 1229 ++++++++++++++++++++++++++++++++++++ tests/testsuite.at | 2 + tests/uuidfilt.pl | 21 + 56 files changed, 11239 insertions(+) create mode 100644 lib/ovsdb-data.c create mode 100644 lib/ovsdb-data.h create mode 100644 lib/ovsdb-error.c create mode 100644 lib/ovsdb-error.h create mode 100644 lib/ovsdb-parser.c create mode 100644 lib/ovsdb-parser.h create mode 100644 lib/ovsdb-types.c create mode 100644 lib/ovsdb-types.h create mode 100644 lib/sort.c create mode 100644 lib/sort.h create mode 100644 ovsdb/SPECS create mode 100644 ovsdb/automake.mk create mode 100644 ovsdb/column.c create mode 100644 ovsdb/column.h create mode 100644 ovsdb/condition.c create mode 100644 ovsdb/condition.h create mode 100644 ovsdb/execution.c create mode 100644 ovsdb/file.c create mode 100644 ovsdb/file.h create mode 100644 ovsdb/jsonrpc-server.c create mode 100644 ovsdb/jsonrpc-server.h create mode 100644 ovsdb/ovsdb-server.c create mode 100644 ovsdb/ovsdb-tool.c create mode 100644 ovsdb/ovsdb.c create mode 100644 ovsdb/ovsdb.h create mode 100644 ovsdb/query.c create mode 100644 ovsdb/query.h create mode 100644 ovsdb/row.c create mode 100644 ovsdb/row.h create mode 100644 ovsdb/table.c create mode 100644 ovsdb/table.h create mode 100644 ovsdb/transaction.c create mode 100644 ovsdb/transaction.h create mode 100644 ovsdb/trigger.c create mode 100644 ovsdb/trigger.h create mode 100644 tests/ovsdb-column.at create mode 100644 tests/ovsdb-condition.at create mode 100644 tests/ovsdb-data.at create mode 100644 tests/ovsdb-execution.at create mode 100644 tests/ovsdb-file.at create mode 100644 tests/ovsdb-query.at create mode 100644 tests/ovsdb-row.at create mode 100644 tests/ovsdb-table.at create mode 100644 tests/ovsdb-transaction.at create mode 100644 tests/ovsdb-trigger.at create mode 100644 tests/ovsdb-types.at create mode 100644 tests/ovsdb.at create mode 100644 tests/test-ovsdb.c create mode 100755 tests/uuidfilt.pl diff --git a/Makefile.am b/Makefile.am index 52f5e594..22ebb219 100644 --- a/Makefile.am +++ b/Makefile.am @@ -77,5 +77,6 @@ include include/automake.mk include third-party/automake.mk include debian/automake.mk include vswitchd/automake.mk +include ovsdb/automake.mk include xenserver/automake.mk include extras/ezio/automake.mk diff --git a/lib/automake.mk b/lib/automake.mk index 07f7ce71..918e4bb4 100644 --- a/lib/automake.mk +++ b/lib/automake.mk @@ -77,6 +77,15 @@ lib_libopenvswitch_a_SOURCES = \ lib/ofp-print.h \ lib/ofpbuf.c \ lib/ofpbuf.h \ + lib/ovsdb-client.h \ + lib/ovsdb-data.c \ + lib/ovsdb-data.h \ + lib/ovsdb-error.c \ + lib/ovsdb-error.h \ + lib/ovsdb-parser.c \ + lib/ovsdb-parser.h \ + lib/ovsdb-types.c \ + lib/ovsdb-types.h \ lib/packets.c \ lib/packets.h \ lib/pcap.c \ @@ -104,6 +113,8 @@ lib_libopenvswitch_a_SOURCES = \ lib/signals.h \ lib/socket-util.c \ lib/socket-util.h \ + lib/sort.c \ + lib/sort.h \ lib/stp.c \ lib/stp.h \ lib/stream-fd.c \ diff --git a/lib/compiler.h b/lib/compiler.h index 17e245fc..216dd6af 100644 --- a/lib/compiler.h +++ b/lib/compiler.h @@ -26,5 +26,6 @@ #define ALWAYS_INLINE __attribute__((always_inline)) #define likely(x) __builtin_expect((x),1) #define unlikely(x) __builtin_expect((x),0) +#define WARN_UNUSED_RESULT __attribute__((__warn_unused_result__)) #endif /* compiler.h */ diff --git a/lib/ovsdb-data.c b/lib/ovsdb-data.c new file mode 100644 index 00000000..46298cb4 --- /dev/null +++ b/lib/ovsdb-data.c @@ -0,0 +1,756 @@ +/* Copyright (c) 2009 Nicira Networks + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "ovsdb-data.h" + +#include + +#include "hash.h" +#include "ovsdb-error.h" +#include "json.h" +#include "shash.h" +#include "sort.h" + +static struct json * +wrap_json(const char *name, struct json *wrapped) +{ + return json_array_create_2(json_string_create(name), wrapped); +} + +void +ovsdb_atom_init_default(union ovsdb_atom *atom, enum ovsdb_atomic_type type) +{ + switch (type) { + case OVSDB_TYPE_VOID: + NOT_REACHED(); + + case OVSDB_TYPE_INTEGER: + atom->integer = 0; + break; + + case OVSDB_TYPE_REAL: + atom->real = 0.0; + break; + + case OVSDB_TYPE_BOOLEAN: + atom->boolean = false; + break; + + case OVSDB_TYPE_STRING: + atom->string = xmemdup("", 1); + break; + + case OVSDB_TYPE_UUID: + uuid_zero(&atom->uuid); + break; + + case OVSDB_N_TYPES: + default: + NOT_REACHED(); + } +} + +void +ovsdb_atom_clone(union ovsdb_atom *new, const union ovsdb_atom *old, + enum ovsdb_atomic_type type) +{ + switch (type) { + case OVSDB_TYPE_VOID: + NOT_REACHED(); + + case OVSDB_TYPE_INTEGER: + new->integer = old->integer; + break; + + case OVSDB_TYPE_REAL: + new->real = old->real; + break; + + case OVSDB_TYPE_BOOLEAN: + new->boolean = old->boolean; + break; + + case OVSDB_TYPE_STRING: + new->string = xstrdup(old->string); + break; + + case OVSDB_TYPE_UUID: + new->uuid = old->uuid; + break; + + case OVSDB_N_TYPES: + default: + NOT_REACHED(); + } +} + +void +ovsdb_atom_swap(union ovsdb_atom *a, union ovsdb_atom *b) +{ + union ovsdb_atom tmp = *a; + *a = *b; + *b = tmp; +} + +uint32_t +ovsdb_atom_hash(const union ovsdb_atom *atom, enum ovsdb_atomic_type type, + uint32_t basis) +{ + switch (type) { + case OVSDB_TYPE_VOID: + NOT_REACHED(); + + case OVSDB_TYPE_INTEGER: + return hash_int(atom->integer, basis); + + case OVSDB_TYPE_REAL: + return hash_double(atom->real, basis); + + case OVSDB_TYPE_BOOLEAN: + return hash_boolean(atom->boolean, basis); + + case OVSDB_TYPE_STRING: + return hash_string(atom->string, basis); + + case OVSDB_TYPE_UUID: + return hash_int(uuid_hash(&atom->uuid), basis); + + case OVSDB_N_TYPES: + default: + NOT_REACHED(); + } +} + +int +ovsdb_atom_compare_3way(const union ovsdb_atom *a, + const union ovsdb_atom *b, + enum ovsdb_atomic_type type) +{ + switch (type) { + case OVSDB_TYPE_VOID: + NOT_REACHED(); + + case OVSDB_TYPE_INTEGER: + return a->integer < b->integer ? -1 : a->integer > b->integer; + + case OVSDB_TYPE_REAL: + return a->real < b->real ? -1 : a->real > b->real; + + case OVSDB_TYPE_BOOLEAN: + return a->boolean - b->boolean; + + case OVSDB_TYPE_STRING: + return strcmp(a->string, b->string); + + case OVSDB_TYPE_UUID: + return uuid_compare_3way(&a->uuid, &b->uuid); + + case OVSDB_N_TYPES: + default: + NOT_REACHED(); + } +} + +static struct ovsdb_error * +unwrap_json(const struct json *json, const char *name, + enum json_type value_type, const struct json **value) +{ + if (json->type != JSON_ARRAY + || json->u.array.n != 2 + || json->u.array.elems[0]->type != JSON_STRING + || (name && strcmp(json->u.array.elems[0]->u.string, name)) + || json->u.array.elems[1]->type != value_type) + { + return ovsdb_syntax_error(json, NULL, "expected [\"%s\", <%s>]", name, + json_type_to_string(value_type)); + } + *value = json->u.array.elems[1]; + return NULL; +} + +static struct ovsdb_error * +parse_json_pair(const struct json *json, + const struct json **elem0, const struct json **elem1) +{ + if (json->type != JSON_ARRAY || json->u.array.n != 2) { + return ovsdb_syntax_error(json, NULL, "expected 2-element array"); + } + *elem0 = json->u.array.elems[0]; + *elem1 = json->u.array.elems[1]; + return NULL; +} + +static struct ovsdb_error * +ovsdb_atom_parse_uuid(struct uuid *uuid, const struct json *json, + const struct ovsdb_symbol_table *symtab) + WARN_UNUSED_RESULT; + +static struct ovsdb_error * +ovsdb_atom_parse_uuid(struct uuid *uuid, const struct json *json, + const struct ovsdb_symbol_table *symtab) +{ + struct ovsdb_error *error0; + const struct json *value; + + error0 = unwrap_json(json, "uuid", JSON_STRING, &value); + if (!error0) { + const char *uuid_string = json_string(value); + if (!uuid_from_string(uuid, uuid_string)) { + return ovsdb_syntax_error(json, NULL, "\"%s\" is not a valid UUID", + uuid_string); + } + } else if (symtab) { + struct ovsdb_error *error1; + + error1 = unwrap_json(json, "named-uuid", JSON_STRING, &value); + if (!error1) { + const char *name = json_string(value); + const struct uuid *named_uuid; + + ovsdb_error_destroy(error0); + + named_uuid = ovsdb_symbol_table_get(symtab, name); + if (named_uuid) { + *uuid = *named_uuid; + return NULL; + } else { + return ovsdb_syntax_error(json, NULL, + "unknown named-uuid \"%s\"", name); + } + } + ovsdb_error_destroy(error1); + } + + return error0; +} + +struct ovsdb_error * +ovsdb_atom_from_json(union ovsdb_atom *atom, enum ovsdb_atomic_type type, + const struct json *json, + const struct ovsdb_symbol_table *symtab) +{ + switch (type) { + case OVSDB_TYPE_VOID: + NOT_REACHED(); + + case OVSDB_TYPE_INTEGER: + if (json->type == JSON_INTEGER) { + atom->integer = json->u.integer; + return NULL; + } + break; + + case OVSDB_TYPE_REAL: + if (json->type == JSON_INTEGER) { + atom->real = json->u.integer; + return NULL; + } else if (json->type == JSON_REAL) { + atom->real = json->u.real; + return NULL; + } + break; + + case OVSDB_TYPE_BOOLEAN: + if (json->type == JSON_TRUE) { + atom->boolean = true; + return NULL; + } else if (json->type == JSON_FALSE) { + atom->boolean = false; + return NULL; + } + break; + + case OVSDB_TYPE_STRING: + if (json->type == JSON_STRING) { + atom->string = xstrdup(json->u.string); + return NULL; + } + break; + + case OVSDB_TYPE_UUID: + return ovsdb_atom_parse_uuid(&atom->uuid, json, symtab); + + case OVSDB_N_TYPES: + default: + NOT_REACHED(); + } + + return ovsdb_syntax_error(json, NULL, "expected %s", + ovsdb_atomic_type_to_string(type)); +} + +struct json * +ovsdb_atom_to_json(const union ovsdb_atom *atom, enum ovsdb_atomic_type type) +{ + switch (type) { + case OVSDB_TYPE_VOID: + NOT_REACHED(); + + case OVSDB_TYPE_INTEGER: + return json_integer_create(atom->integer); + + case OVSDB_TYPE_REAL: + return json_real_create(atom->real); + + case OVSDB_TYPE_BOOLEAN: + return json_boolean_create(atom->boolean); + + case OVSDB_TYPE_STRING: + return json_string_create(atom->string); + + case OVSDB_TYPE_UUID: + return wrap_json("uuid", json_string_create_nocopy( + xasprintf(UUID_FMT, UUID_ARGS(&atom->uuid)))); + + case OVSDB_N_TYPES: + default: + NOT_REACHED(); + } +} + +static union ovsdb_atom * +alloc_default_atoms(enum ovsdb_atomic_type type, size_t n) +{ + if (type != OVSDB_TYPE_VOID && n) { + union ovsdb_atom *atoms; + unsigned int i; + + atoms = xmalloc(n * sizeof *atoms); + for (i = 0; i < n; i++) { + ovsdb_atom_init_default(&atoms[i], type); + } + return atoms; + } else { + /* Avoid wasting memory in the n == 0 case, because xmalloc(0) is + * treated as xmalloc(1). */ + return NULL; + } +} + +void +ovsdb_datum_init_default(struct ovsdb_datum *datum, + const struct ovsdb_type *type) +{ + datum->n = type->n_min; + datum->keys = alloc_default_atoms(type->key_type, datum->n); + datum->values = alloc_default_atoms(type->value_type, datum->n); +} + +static union ovsdb_atom * +clone_atoms(const union ovsdb_atom *old, enum ovsdb_atomic_type type, size_t n) +{ + if (type != OVSDB_TYPE_VOID && n) { + union ovsdb_atom *new; + unsigned int i; + + new = xmalloc(n * sizeof *new); + for (i = 0; i < n; i++) { + ovsdb_atom_clone(&new[i], &old[i], type); + } + return new; + } else { + /* Avoid wasting memory in the n == 0 case, because xmalloc(0) is + * treated as xmalloc(1). */ + return NULL; + } +} + +void +ovsdb_datum_clone(struct ovsdb_datum *new, const struct ovsdb_datum *old, + const struct ovsdb_type *type) +{ + unsigned int n = old->n; + new->n = n; + new->keys = clone_atoms(old->keys, type->key_type, n); + new->values = clone_atoms(old->values, type->value_type, n); +} + +static void +free_data(enum ovsdb_atomic_type type, + union ovsdb_atom *atoms, size_t n_atoms) +{ + if (ovsdb_atom_needs_destruction(type)) { + unsigned int i; + for (i = 0; i < n_atoms; i++) { + ovsdb_atom_destroy(&atoms[i], type); + } + } + free(atoms); +} + +void +ovsdb_datum_destroy(struct ovsdb_datum *datum, const struct ovsdb_type *type) +{ + free_data(type->key_type, datum->keys, datum->n); + free_data(type->value_type, datum->values, datum->n); +} + +void +ovsdb_datum_swap(struct ovsdb_datum *a, struct ovsdb_datum *b) +{ + struct ovsdb_datum tmp = *a; + *a = *b; + *b = tmp; +} + +struct ovsdb_datum_sort_cbdata { + const struct ovsdb_type *type; + struct ovsdb_datum *datum; +}; + +static int +ovsdb_datum_sort_compare_cb(size_t a, size_t b, void *cbdata_) +{ + struct ovsdb_datum_sort_cbdata *cbdata = cbdata_; + + return ovsdb_atom_compare_3way(&cbdata->datum->keys[a], + &cbdata->datum->keys[b], + cbdata->type->key_type); +} + +static void +ovsdb_datum_sort_swap_cb(size_t a, size_t b, void *cbdata_) +{ + struct ovsdb_datum_sort_cbdata *cbdata = cbdata_; + + ovsdb_atom_swap(&cbdata->datum->keys[a], &cbdata->datum->keys[b]); + if (cbdata->type->value_type != OVSDB_TYPE_VOID) { + ovsdb_atom_swap(&cbdata->datum->values[a], &cbdata->datum->values[b]); + } +} + +static struct ovsdb_error * +ovsdb_datum_sort(struct ovsdb_datum *datum, const struct ovsdb_type *type) +{ + if (datum->n < 2) { + return NULL; + } else { + struct ovsdb_datum_sort_cbdata cbdata; + size_t i; + + cbdata.type = type; + cbdata.datum = datum; + sort(datum->n, ovsdb_datum_sort_compare_cb, ovsdb_datum_sort_swap_cb, + &cbdata); + + for (i = 0; i < datum->n - 1; i++) { + if (ovsdb_atom_equals(&datum->keys[i], &datum->keys[i + 1], + type->key_type)) { + if (ovsdb_type_is_map(type)) { + return ovsdb_error(NULL, "map contains duplicate key"); + } else { + return ovsdb_error(NULL, "set contains duplicate"); + } + } + } + + return NULL; + } +} + +struct ovsdb_error * +ovsdb_datum_from_json(struct ovsdb_datum *datum, + const struct ovsdb_type *type, + const struct json *json, + const struct ovsdb_symbol_table *symtab) +{ + struct ovsdb_error *error; + + if (ovsdb_type_is_scalar(type)) { + datum->n = 1; + datum->keys = xmalloc(sizeof *datum->keys); + datum->values = NULL; + + error = ovsdb_atom_from_json(&datum->keys[0], type->key_type, + json, symtab); + if (error) { + free(datum->keys); + } + return error; + } else { + bool is_map = ovsdb_type_is_map(type); + const char *class = is_map ? "map" : "set"; + const struct json *inner; + unsigned int i; + size_t n; + + assert(is_map || ovsdb_type_is_set(type)); + + error = unwrap_json(json, class, JSON_ARRAY, &inner); + if (error) { + return error; + } + + n = inner->u.array.n; + if (n < type->n_min || n > type->n_max) { + return ovsdb_syntax_error(json, NULL, "%s must have %u to " + "%u members but %zu are present", + class, type->n_min, type->n_max, n); + } + + datum->n = 0; + datum->keys = xmalloc(n * sizeof *datum->keys); + datum->values = is_map ? xmalloc(n * sizeof *datum->values) : NULL; + for (i = 0; i < n; i++) { + const struct json *element = inner->u.array.elems[i]; + const struct json *key = NULL; + const struct json *value = NULL; + + if (!is_map) { + key = element; + } else { + error = parse_json_pair(element, &key, &value); + if (error) { + goto error; + } + } + + error = ovsdb_atom_from_json(&datum->keys[i], type->key_type, + key, symtab); + if (error) { + goto error; + } + + if (is_map) { + error = ovsdb_atom_from_json(&datum->values[i], + type->value_type, value, symtab); + if (error) { + ovsdb_atom_destroy(&datum->keys[i], type->key_type); + goto error; + } + } + + datum->n++; + } + + error = ovsdb_datum_sort(datum, type); + if (error) { + goto error; + } + + return NULL; + + error: + ovsdb_datum_destroy(datum, type); + return error; + } +} + +struct json * +ovsdb_datum_to_json(const struct ovsdb_datum *datum, + const struct ovsdb_type *type) +{ + /* These tests somewhat tolerate a 'datum' that does not exactly match + * 'type', in particular a datum with 'n' not in the allowed range. */ + if (datum->n == 1 && ovsdb_type_is_scalar(type)) { + return ovsdb_atom_to_json(&datum->keys[0], type->key_type); + } else if (type->value_type == OVSDB_TYPE_VOID) { + struct json **elems; + size_t i; + + elems = xmalloc(datum->n * sizeof *elems); + for (i = 0; i < datum->n; i++) { + elems[i] = ovsdb_atom_to_json(&datum->keys[i], type->key_type); + } + + return wrap_json("set", json_array_create(elems, datum->n)); + } else { + struct json **elems; + size_t i; + + elems = xmalloc(datum->n * sizeof *elems); + for (i = 0; i < datum->n; i++) { + elems[i] = json_array_create_2( + ovsdb_atom_to_json(&datum->keys[i], type->key_type), + ovsdb_atom_to_json(&datum->values[i], type->value_type)); + } + + return wrap_json("map", json_array_create(elems, datum->n)); + } +} + +static uint32_t +hash_atoms(enum ovsdb_atomic_type type, const union ovsdb_atom *atoms, + unsigned int n, uint32_t basis) +{ + if (type != OVSDB_TYPE_VOID) { + unsigned int i; + + for (i = 0; i < n; i++) { + basis = ovsdb_atom_hash(&atoms[i], type, basis); + } + } + return basis; +} + +uint32_t +ovsdb_datum_hash(const struct ovsdb_datum *datum, + const struct ovsdb_type *type, uint32_t basis) +{ + basis = hash_atoms(type->key_type, datum->keys, datum->n, basis); + basis ^= (type->key_type << 24) | (type->value_type << 16) | datum->n; + basis = hash_atoms(type->value_type, datum->values, datum->n, basis); + return basis; +} + +static int +atom_arrays_compare_3way(const union ovsdb_atom *a, + const union ovsdb_atom *b, + enum ovsdb_atomic_type type, + size_t n) +{ + unsigned int i; + + for (i = 0; i < n; i++) { + int cmp = ovsdb_atom_compare_3way(&a[i], &b[i], type); + if (cmp) { + return cmp; + } + } + + return 0; +} + +bool +ovsdb_datum_equals(const struct ovsdb_datum *a, + const struct ovsdb_datum *b, + const struct ovsdb_type *type) +{ + return !ovsdb_datum_compare_3way(a, b, type); +} + +int +ovsdb_datum_compare_3way(const struct ovsdb_datum *a, + const struct ovsdb_datum *b, + const struct ovsdb_type *type) +{ + int cmp; + + if (a->n != b->n) { + return a->n < b->n ? -1 : 1; + } + + cmp = atom_arrays_compare_3way(a->keys, b->keys, type->key_type, a->n); + if (cmp) { + return cmp; + } + + return (type->value_type == OVSDB_TYPE_VOID ? 0 + : atom_arrays_compare_3way(a->values, b->values, type->value_type, + a->n)); +} + +static bool +ovsdb_datum_contains(const struct ovsdb_datum *a, int i, + const struct ovsdb_datum *b, + const struct ovsdb_type *type) +{ + int low = 0; + int high = b->n; + while (low < high) { + int j = (low + high) / 2; + int cmp = ovsdb_atom_compare_3way(&a->keys[i], &b->keys[j], type->key_type); + if (cmp < 0) { + high = j; + } else if (cmp > 0) { + low = j + 1; + } else { + return (type->value_type == OVSDB_TYPE_VOID + || ovsdb_atom_equals(&a->values[i], &b->values[j], + type->value_type)); + } + } + return false; +} + +/* Returns true if every element in 'a' is also in 'b', false otherwise. */ +bool +ovsdb_datum_includes_all(const struct ovsdb_datum *a, + const struct ovsdb_datum *b, + const struct ovsdb_type *type) +{ + size_t i; + + for (i = 0; i < a->n; i++) { + if (!ovsdb_datum_contains(a, i, b, type)) { + return false; + } + } + return true; +} + +/* Returns true if no element in 'a' is also in 'b', false otherwise. */ +bool +ovsdb_datum_excludes_all(const struct ovsdb_datum *a, + const struct ovsdb_datum *b, + const struct ovsdb_type *type) +{ + size_t i; + + for (i = 0; i < a->n; i++) { + if (ovsdb_datum_contains(a, i, b, type)) { + return false; + } + } + return true; +} + +struct ovsdb_symbol_table { + struct shash sh; +}; + +struct ovsdb_symbol_table * +ovsdb_symbol_table_create(void) +{ + struct ovsdb_symbol_table *symtab = xmalloc(sizeof *symtab); + shash_init(&symtab->sh); + return symtab; +} + +void +ovsdb_symbol_table_destroy(struct ovsdb_symbol_table *symtab) +{ + if (symtab) { + struct shash_node *node, *next; + + SHASH_FOR_EACH_SAFE (node, next, &symtab->sh) { + free(node->data); + shash_delete(&symtab->sh, node); + } + shash_destroy(&symtab->sh); + free(symtab); + } +} + +const struct uuid * +ovsdb_symbol_table_get(const struct ovsdb_symbol_table *symtab, + const char *name) +{ + return shash_find_data(&symtab->sh, name); +} + +void +ovsdb_symbol_table_put(struct ovsdb_symbol_table *symtab, const char *name, + const struct uuid *uuid) +{ + struct uuid *entry = shash_find_data(&symtab->sh, name); + if (!entry) { + shash_add(&symtab->sh, name, xmemdup(uuid, sizeof *uuid)); + } else { + *entry = *uuid; + } +} diff --git a/lib/ovsdb-data.h b/lib/ovsdb-data.h new file mode 100644 index 00000000..35c4e307 --- /dev/null +++ b/lib/ovsdb-data.h @@ -0,0 +1,128 @@ +/* Copyright (c) 2009 Nicira Networks + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OVSDB_DATA_H +#define OVSDB_DATA_H 1 + +#include +#include "compiler.h" +#include "ovsdb-types.h" + +struct ovsdb_symbol_table; + +/* One value of an atomic type (given by enum ovs_atomic_type). */ +union ovsdb_atom { + int64_t integer; + double real; + bool boolean; + char *string; + struct uuid uuid; +}; + +void ovsdb_atom_init_default(union ovsdb_atom *, enum ovsdb_atomic_type); +void ovsdb_atom_clone(union ovsdb_atom *, const union ovsdb_atom *, + enum ovsdb_atomic_type); +void ovsdb_atom_swap(union ovsdb_atom *, union ovsdb_atom *); + +static inline bool +ovsdb_atom_needs_destruction(enum ovsdb_atomic_type type) +{ + return type == OVSDB_TYPE_STRING; +} + +static inline void +ovsdb_atom_destroy(union ovsdb_atom *atom, enum ovsdb_atomic_type type) +{ + if (type == OVSDB_TYPE_STRING) { + free(atom->string); + } +} + +uint32_t ovsdb_atom_hash(const union ovsdb_atom *, enum ovsdb_atomic_type, + uint32_t basis); + +int ovsdb_atom_compare_3way(const union ovsdb_atom *, + const union ovsdb_atom *, + enum ovsdb_atomic_type); + +static inline bool ovsdb_atom_equals(const union ovsdb_atom *a, + const union ovsdb_atom *b, + enum ovsdb_atomic_type type) +{ + return !ovsdb_atom_compare_3way(a, b, type); +} + +struct ovsdb_error *ovsdb_atom_from_json(union ovsdb_atom *, + enum ovsdb_atomic_type, + const struct json *, + const struct ovsdb_symbol_table *) + WARN_UNUSED_RESULT; +struct json *ovsdb_atom_to_json(const union ovsdb_atom *, + enum ovsdb_atomic_type); + +/* One value of an OVSDB type (given by struct ovsdb_type). */ +struct ovsdb_datum { + unsigned int n; /* Number of 'keys' and 'values'. */ + union ovsdb_atom *keys; /* Each of the ovsdb_type's 'key_type'. */ + union ovsdb_atom *values; /* Each of the ovsdb_type's 'value_type'. */ +}; + +void ovsdb_datum_init_default(struct ovsdb_datum *, const struct ovsdb_type *); +void ovsdb_datum_clone(struct ovsdb_datum *, const struct ovsdb_datum *, + const struct ovsdb_type *); +void ovsdb_datum_destroy(struct ovsdb_datum *, const struct ovsdb_type *); +void ovsdb_datum_swap(struct ovsdb_datum *, struct ovsdb_datum *); + +struct ovsdb_error *ovsdb_datum_from_json(struct ovsdb_datum *, + const struct ovsdb_type *, + const struct json *, + const struct ovsdb_symbol_table *) + WARN_UNUSED_RESULT; +struct json *ovsdb_datum_to_json(const struct ovsdb_datum *, + const struct ovsdb_type *); + +uint32_t ovsdb_datum_hash(const struct ovsdb_datum *, + const struct ovsdb_type *, uint32_t basis); +int ovsdb_datum_compare_3way(const struct ovsdb_datum *, + const struct ovsdb_datum *, + const struct ovsdb_type *); +bool ovsdb_datum_equals(const struct ovsdb_datum *, + const struct ovsdb_datum *, + const struct ovsdb_type *); +bool ovsdb_datum_includes_all(const struct ovsdb_datum *, + const struct ovsdb_datum *, + const struct ovsdb_type *); +bool ovsdb_datum_excludes_all(const struct ovsdb_datum *, + const struct ovsdb_datum *, + const struct ovsdb_type *); + +static inline bool +ovsdb_datum_conforms_to_type(const struct ovsdb_datum *datum, + const struct ovsdb_type *type) +{ + return datum->n >= type->n_min && datum->n <= type->n_max; +} + +/* A table mapping from names to data items. Currently the data items are + * always UUIDs; perhaps this will be expanded in the future. */ + +struct ovsdb_symbol_table *ovsdb_symbol_table_create(void); +void ovsdb_symbol_table_destroy(struct ovsdb_symbol_table *); +const struct uuid *ovsdb_symbol_table_get(const struct ovsdb_symbol_table *, + const char *name); +void ovsdb_symbol_table_put(struct ovsdb_symbol_table *, const char *name, + const struct uuid *); + +#endif /* ovsdb-data.h */ diff --git a/lib/ovsdb-error.c b/lib/ovsdb-error.c new file mode 100644 index 00000000..c0eddf28 --- /dev/null +++ b/lib/ovsdb-error.c @@ -0,0 +1,221 @@ +/* Copyright (c) 2009 Nicira Networks + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "ovsdb-error.h" + +#include + +#include "backtrace.h" +#include "dynamic-string.h" +#include "json.h" +#include "util.h" + +struct ovsdb_error { + const char *tag; /* String for "error" member. */ + char *details; /* String for "details" member. */ + char *syntax; /* String for "syntax" member. */ + int errno_; /* Unix errno value, 0 if none. */ +}; + +static struct ovsdb_error * +ovsdb_error_valist(const char *tag, const char *details, va_list args) +{ + struct ovsdb_error *error = xmalloc(sizeof *error); + error->tag = tag ? tag : "ovsdb error"; + error->details = details ? xvasprintf(details, args) : NULL; + error->syntax = NULL; + error->errno_ = 0; + return error; +} + +struct ovsdb_error * +ovsdb_error(const char *tag, const char *details, ...) +{ + struct ovsdb_error *error; + va_list args; + + va_start(args, details); + error = ovsdb_error_valist(tag, details, args); + va_end(args); + + return error; +} + +struct ovsdb_error * +ovsdb_io_error(int errno_, const char *details, ...) +{ + struct ovsdb_error *error; + va_list args; + + va_start(args, details); + error = ovsdb_error_valist("I/O error", details, args); + va_end(args); + + error->errno_ = errno_; + + return error; +} + +struct ovsdb_error * +ovsdb_syntax_error(const struct json *json, const char *tag, + const char *details, ...) +{ + struct ovsdb_error *error; + va_list args; + + va_start(args, details); + error = ovsdb_error_valist(tag ? tag : "syntax error", details, args); + va_end(args); + + if (json) { + /* XXX this is much too much information in some cases */ + error->syntax = json_to_string(json, 0); + } + + return error; +} + +struct ovsdb_error * +ovsdb_wrap_error(struct ovsdb_error *error, const char *details, ...) +{ + va_list args; + char *msg; + + va_start(args, details); + msg = xvasprintf(details, args); + va_end(args); + + if (error->details) { + char *new = xasprintf("%s: %s", msg, error->details); + free(error->details); + error->details = new; + free(msg); + } else { + error->details = msg; + } + + return error; +} + +struct ovsdb_error * +ovsdb_internal_error(const char *file, int line, const char *details, ...) +{ + struct ds ds = DS_EMPTY_INITIALIZER; + struct backtrace backtrace; + struct ovsdb_error *error; + va_list args; + + ds_put_format(&ds, "%s:%d:", file, line); + + if (details) { + ds_put_char(&ds, ' '); + va_start(args, details); + ds_put_format_valist(&ds, details, args); + va_end(args); + } + + backtrace_capture(&backtrace); + if (backtrace.n_frames) { + int i; + + ds_put_cstr(&ds, " (backtrace:"); + for (i = 0; i < backtrace.n_frames; i++) { + ds_put_format(&ds, " 0x%08"PRIxPTR, backtrace.frames[i]); + } + ds_put_char(&ds, ')'); + } + + ds_put_format(&ds, " (%s %s%s)", program_name, VERSION, BUILDNR); + + error = ovsdb_error("internal error", "%s", ds_cstr(&ds)); + + ds_destroy(&ds); + + return error; +} + +void +ovsdb_error_destroy(struct ovsdb_error *error) +{ + if (error) { + free(error->details); + free(error->syntax); + free(error); + } +} + +struct ovsdb_error * +ovsdb_error_clone(const struct ovsdb_error *old) +{ + if (old) { + struct ovsdb_error *new = xmalloc(sizeof *new); + new->tag = old->tag; + new->details = old->details ? xstrdup(old->details) : NULL; + new->syntax = old->syntax ? xstrdup(old->syntax) : NULL; + new->errno_ = old->errno_; + return new; + } else { + return NULL; + } +} + +static const char * +ovsdb_errno_string(int error) +{ + return error == EOF ? "unexpected end of file" : strerror(error); +} + +struct json * +ovsdb_error_to_json(const struct ovsdb_error *error) +{ + struct json *json = json_object_create(); + json_object_put_string(json, "error", error->tag); + if (error->details) { + json_object_put_string(json, "details", error->details); + } + if (error->syntax) { + json_object_put_string(json, "syntax", error->syntax); + } + if (error->errno_) { + json_object_put_string(json, "io-error", + ovsdb_errno_string(error->errno_)); + } + return json; +} + +char * +ovsdb_error_to_string(const struct ovsdb_error *error) +{ + struct ds ds = DS_EMPTY_INITIALIZER; + if (error->syntax) { + ds_put_format(&ds, "syntax \"%s\": ", error->syntax); + } + ds_put_cstr(&ds, error->tag); + if (error->details) { + ds_put_format(&ds, ": %s", error->details); + } + if (error->errno_) { + ds_put_format(&ds, " (%s)", ovsdb_errno_string(error->errno_)); + } + return ds_steal_cstr(&ds); +} + +const char * +ovsdb_error_get_tag(const struct ovsdb_error *error) +{ + return error->tag; +} diff --git a/lib/ovsdb-error.h b/lib/ovsdb-error.h new file mode 100644 index 00000000..7e2523e3 --- /dev/null +++ b/lib/ovsdb-error.h @@ -0,0 +1,53 @@ +/* Copyright (c) 2009 Nicira Networks + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OVSDB_ERROR_H +#define OVSDB_ERROR_H 1 + +#include "compiler.h" + +struct json; + +struct ovsdb_error *ovsdb_error(const char *tag, const char *details, ...) + PRINTF_FORMAT(2, 3) + WARN_UNUSED_RESULT; +struct ovsdb_error *ovsdb_io_error(int error, const char *details, ...) + PRINTF_FORMAT(2, 3) + WARN_UNUSED_RESULT; +struct ovsdb_error *ovsdb_syntax_error(const struct json *, const char *tag, + const char *details, ...) + PRINTF_FORMAT(3, 4) + WARN_UNUSED_RESULT; + +struct ovsdb_error *ovsdb_wrap_error(struct ovsdb_error *error, + const char *details, ...) + PRINTF_FORMAT(2, 3); + +struct ovsdb_error *ovsdb_internal_error(const char *file, int line, + const char *details, ...) + PRINTF_FORMAT(3, 4) + WARN_UNUSED_RESULT; +#define OVSDB_BUG(MSG) ovsdb_internal_error(__FILE__, __LINE__, "%s", MSG) + +void ovsdb_error_destroy(struct ovsdb_error *); +struct ovsdb_error *ovsdb_error_clone(const struct ovsdb_error *) + WARN_UNUSED_RESULT; + +char *ovsdb_error_to_string(const struct ovsdb_error *); +struct json *ovsdb_error_to_json(const struct ovsdb_error *); + +const char *ovsdb_error_get_tag(const struct ovsdb_error *); + +#endif /* ovsdb-error.h */ diff --git a/lib/ovsdb-parser.c b/lib/ovsdb-parser.c new file mode 100644 index 00000000..d923d21a --- /dev/null +++ b/lib/ovsdb-parser.c @@ -0,0 +1,167 @@ +/* Copyright (c) 2009 Nicira Networks + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "ovsdb-parser.h" + +#include +#include + +#include "ovsdb-error.h" + +void +ovsdb_parser_init(struct ovsdb_parser *parser, const struct json *json, + const char *name, ...) +{ + va_list args; + + va_start(args, name); + parser->name = xvasprintf(name, args); + va_end(args); + + svec_init(&parser->used); + parser->error = NULL; + + parser->json = (json && json->type == JSON_OBJECT ? json : NULL); + if (!parser->json) { + ovsdb_parser_raise_error(parser, "Object expected."); + } +} + +static bool +is_id(const char *string) +{ + unsigned char c; + + c = *string; + if (!isalpha(c) && c != '_') { + return false; + } + + for (;;) { + c = *++string; + if (c == '\0') { + return true; + } else if (!isalpha(c) && !isdigit(c) && c != '_') { + return false; + } + } +} + +const struct json * +ovsdb_parser_member(struct ovsdb_parser *parser, const char *name, + enum ovsdb_parser_types types) +{ + struct json *value; + + if (!parser->json) { + return NULL; + } + + value = shash_find_data(json_object(parser->json), name); + if (!value) { + if (!(types & OP_OPTIONAL)) { + ovsdb_parser_raise_error(parser, + "Required '%s' member is missing.", name); + } + return NULL; + } + + if (value->type >= 0 && value->type < JSON_N_TYPES + && (types & (1u << value->type) + || (types & OP_ID + && value->type == JSON_STRING + && is_id(value->u.string)))) + { + svec_add(&parser->used, name); + return value; + } else { + ovsdb_parser_raise_error(parser, "Type mismatch for member '%s'.", + name); + return NULL; + } +} + +void +ovsdb_parser_raise_error(struct ovsdb_parser *parser, const char *format, ...) +{ + if (!parser->error) { + struct ovsdb_error *error; + va_list args; + char *message; + + va_start(args, format); + message = xvasprintf(format, args); + va_end(args); + + error = ovsdb_syntax_error(parser->json, NULL, "Parsing %s failed: %s", + parser->name, message); + free(message); + + parser->error = error; + } +} + +struct ovsdb_error * +ovsdb_parser_get_error(const struct ovsdb_parser *parser) +{ + return parser->error ? ovsdb_error_clone(parser->error) : NULL; +} + +bool +ovsdb_parser_has_error(const struct ovsdb_parser *parser) +{ + return parser->error != NULL; +} + +struct ovsdb_error * +ovsdb_parser_finish(struct ovsdb_parser *parser) +{ + if (!parser->error) { + const struct shash *object = json_object(parser->json); + size_t n_unused; + + /* XXX this method of detecting unused members can be made cheaper */ + svec_sort_unique(&parser->used); + n_unused = shash_count(object) - parser->used.n; + if (n_unused) { + struct shash_node *node; + + SHASH_FOR_EACH (node, object) { + if (!svec_contains(&parser->used, node->name)) { + if (n_unused > 1) { + ovsdb_parser_raise_error( + parser, + "Member '%s' and %zu other member%s " + "are present but not allowed here.", + node->name, n_unused - 1, n_unused > 2 ? "s" : ""); + } else { + ovsdb_parser_raise_error( + parser, + "Member '%s' is present but not allowed here.", + node->name); + } + break; + } + } + } + } + + free(parser->name); + svec_destroy(&parser->used); + + return parser->error; +} diff --git a/lib/ovsdb-parser.h b/lib/ovsdb-parser.h new file mode 100644 index 00000000..f9a2ef7e --- /dev/null +++ b/lib/ovsdb-parser.h @@ -0,0 +1,74 @@ +/* Copyright (c) 2009 Nicira Networks + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OVSDB_PARSER_H +#define OVSDB_PARSER_H 1 + +#include +#include "compiler.h" +#include "json.h" +#include "svec.h" +#include "util.h" + +struct ovsdb_parser { + char *name; /* Used only in error messages. */ + struct svec used; /* Already-parsed names from 'object'. */ + const struct json *json; /* JSON object being parsed. */ + struct ovsdb_error *error; /* Error signaled, if any. */ +}; + +/* Check that the JSON types make the bitwise tricks below work OK. */ +BUILD_ASSERT_DECL(JSON_NULL >= 0 && JSON_NULL < 10); +BUILD_ASSERT_DECL(JSON_FALSE >= 0 && JSON_FALSE < 10); +BUILD_ASSERT_DECL(JSON_TRUE >= 0 && JSON_TRUE < 10); +BUILD_ASSERT_DECL(JSON_OBJECT >= 0 && JSON_OBJECT < 10); +BUILD_ASSERT_DECL(JSON_ARRAY >= 0 && JSON_ARRAY < 10); +BUILD_ASSERT_DECL(JSON_INTEGER >= 0 && JSON_INTEGER < 10); +BUILD_ASSERT_DECL(JSON_REAL >= 0 && JSON_REAL < 10); +BUILD_ASSERT_DECL(JSON_STRING >= 0 && JSON_STRING < 10); +BUILD_ASSERT_DECL(JSON_N_TYPES == 8); + +enum ovsdb_parser_types { + OP_NULL = 1 << JSON_NULL, /* null */ + OP_FALSE = 1 << JSON_FALSE, /* false */ + OP_TRUE = 1 << JSON_TRUE, /* true */ + OP_OBJECT = 1 << JSON_OBJECT, /* {"a": b, "c": d, ...} */ + OP_ARRAY = 1 << JSON_ARRAY, /* [1, 2, 3, ...] */ + OP_INTEGER = 1 << JSON_INTEGER, /* 123. */ + OP_NONINTEGER = 1 << JSON_REAL, /* 123.456. */ + OP_STRING = 1 << JSON_STRING, /* "..." */ + + OP_BOOLEAN = OP_FALSE | OP_TRUE, + OP_NUMBER = OP_INTEGER | OP_NONINTEGER, + + OP_ID = 1 << JSON_N_TYPES, /* "[_a-zA-Z][_a-zA-Z0-9]*" */ + OP_OPTIONAL = 1 << (JSON_N_TYPES + 1) /* no value at all */ +}; + +void ovsdb_parser_init(struct ovsdb_parser *, const struct json *, + const char *name, ...) + PRINTF_FORMAT(3, 4); +const struct json *ovsdb_parser_member(struct ovsdb_parser *, const char *name, + enum ovsdb_parser_types); + +void ovsdb_parser_raise_error(struct ovsdb_parser *parser, + const char *format, ...) + PRINTF_FORMAT(2, 3); +bool ovsdb_parser_has_error(const struct ovsdb_parser *); +struct ovsdb_error *ovsdb_parser_get_error(const struct ovsdb_parser *); +struct ovsdb_error *ovsdb_parser_finish(struct ovsdb_parser *) + WARN_UNUSED_RESULT; + +#endif /* ovsdb-parser.h */ diff --git a/lib/ovsdb-types.c b/lib/ovsdb-types.c new file mode 100644 index 00000000..07982e34 --- /dev/null +++ b/lib/ovsdb-types.c @@ -0,0 +1,251 @@ +/* Copyright (c) 2009 Nicira Networks + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "ovsdb-types.h" + +#include + +#include "dynamic-string.h" +#include "json.h" +#include "ovsdb-error.h" +#include "ovsdb-parser.h" + +const struct ovsdb_type ovsdb_type_integer = + OVSDB_TYPE_SCALAR_INITIALIZER(OVSDB_TYPE_INTEGER); +const struct ovsdb_type ovsdb_type_real = + OVSDB_TYPE_SCALAR_INITIALIZER(OVSDB_TYPE_REAL); +const struct ovsdb_type ovsdb_type_boolean = + OVSDB_TYPE_SCALAR_INITIALIZER(OVSDB_TYPE_BOOLEAN); +const struct ovsdb_type ovsdb_type_string = + OVSDB_TYPE_SCALAR_INITIALIZER(OVSDB_TYPE_STRING); +const struct ovsdb_type ovsdb_type_uuid = + OVSDB_TYPE_SCALAR_INITIALIZER(OVSDB_TYPE_UUID); + +const char * +ovsdb_atomic_type_to_string(enum ovsdb_atomic_type type) +{ + switch (type) { + case OVSDB_TYPE_VOID: + return "void"; + + case OVSDB_TYPE_INTEGER: + return "integer"; + + case OVSDB_TYPE_REAL: + return "real"; + + case OVSDB_TYPE_BOOLEAN: + return "boolean"; + + case OVSDB_TYPE_STRING: + return "string"; + + case OVSDB_TYPE_UUID: + return "uuid"; + + case OVSDB_N_TYPES: + default: + return ""; + } +} + +struct json * +ovsdb_atomic_type_to_json(enum ovsdb_atomic_type type) +{ + return json_string_create(ovsdb_atomic_type_to_string(type)); +} + +bool +ovsdb_type_is_valid(const struct ovsdb_type *type) +{ + return (type->key_type != OVSDB_TYPE_VOID + && ovsdb_atomic_type_is_valid(type->key_type) + && ovsdb_atomic_type_is_valid(type->value_type) + && type->n_min <= type->n_max + && (type->value_type == OVSDB_TYPE_VOID + || ovsdb_atomic_type_is_valid_key(type->key_type))); +} + +bool +ovsdb_atomic_type_from_string(const char *string, enum ovsdb_atomic_type *type) +{ + if (!strcmp(string, "integer")) { + *type = OVSDB_TYPE_INTEGER; + } else if (!strcmp(string, "real")) { + *type = OVSDB_TYPE_REAL; + } else if (!strcmp(string, "boolean")) { + *type = OVSDB_TYPE_BOOLEAN; + } else if (!strcmp(string, "string")) { + *type = OVSDB_TYPE_STRING; + } else if (!strcmp(string, "uuid")) { + *type = OVSDB_TYPE_UUID; + } else { + return false; + } + return true; +} + +struct ovsdb_error * +ovsdb_atomic_type_from_json(enum ovsdb_atomic_type *type, + const struct json *json) +{ + if (json->type == JSON_STRING) { + if (ovsdb_atomic_type_from_string(json_string(json), type)) { + return NULL; + } else { + *type = OVSDB_TYPE_VOID; + return ovsdb_syntax_error(json, NULL, + "\"%s\" is not an atomic-type", + json_string(json)); + } + } else { + *type = OVSDB_TYPE_VOID; + return ovsdb_syntax_error(json, NULL, "atomic-type expected"); + } +} + +static struct ovsdb_error * +n_from_json(const struct json *json, unsigned int *n) +{ + if (!json) { + return NULL; + } else if (json->type == JSON_INTEGER + && json->u.integer >= 0 && json->u.integer < UINT_MAX) { + *n = json->u.integer; + return NULL; + } else { + return ovsdb_syntax_error(json, NULL, "bad min or max value"); + } +} + +char * +ovsdb_type_to_english(const struct ovsdb_type *type) +{ + const char *key = ovsdb_atomic_type_to_string(type->key_type); + const char *value = ovsdb_atomic_type_to_string(type->value_type); + if (ovsdb_type_is_scalar(type)) { + return xstrdup(key); + } else { + struct ds s = DS_EMPTY_INITIALIZER; + ds_put_cstr(&s, ovsdb_type_is_set(type) ? "set" : "map"); + if (type->n_max == UINT_MAX) { + if (type->n_min) { + ds_put_format(&s, " of %u or more", type->n_min); + } else { + ds_put_cstr(&s, " of"); + } + } else if (type->n_min) { + ds_put_format(&s, " of %u to %u", type->n_min, type->n_max); + } else { + ds_put_format(&s, " of up to %u", type->n_max); + } + if (ovsdb_type_is_set(type)) { + ds_put_format(&s, " %ss", key); + } else { + ds_put_format(&s, " (%s, %s) pairs", key, value); + } + return ds_cstr(&s); + } +} + +struct ovsdb_error * +ovsdb_type_from_json(struct ovsdb_type *type, const struct json *json) +{ + type->value_type = OVSDB_TYPE_VOID; + type->n_min = 1; + type->n_max = 1; + + if (json->type == JSON_STRING) { + return ovsdb_atomic_type_from_json(&type->key_type, json); + } else if (json->type == JSON_OBJECT) { + const struct json *key, *value, *min, *max; + struct ovsdb_error *error; + struct ovsdb_parser parser; + + ovsdb_parser_init(&parser, json, "ovsdb type"); + key = ovsdb_parser_member(&parser, "key", OP_STRING); + value = ovsdb_parser_member(&parser, "value", OP_STRING | OP_OPTIONAL); + min = ovsdb_parser_member(&parser, "min", OP_INTEGER | OP_OPTIONAL); + max = ovsdb_parser_member(&parser, "max", + OP_INTEGER | OP_STRING | OP_OPTIONAL); + error = ovsdb_parser_finish(&parser); + if (error) { + return error; + } + + error = ovsdb_atomic_type_from_json(&type->key_type, key); + if (error) { + return error; + } + + if (value) { + error = ovsdb_atomic_type_from_json(&type->value_type, value); + if (error) { + return error; + } + } + + error = n_from_json(min, &type->n_min); + if (error) { + return error; + } + + if (max && max->type == JSON_STRING + && !strcmp(max->u.string, "unlimited")) { + type->n_max = UINT_MAX; + } else { + error = n_from_json(max, &type->n_max); + if (error) { + return error; + } + } + + if (!ovsdb_type_is_valid(type)) { + return ovsdb_syntax_error(json, NULL, + "ovsdb type fails constraint checks"); + } + + return NULL; + } else { + return ovsdb_syntax_error(json, NULL, "ovsdb type expected"); + } +} + +struct json * +ovsdb_type_to_json(const struct ovsdb_type *type) +{ + if (ovsdb_type_is_scalar(type)) { + return ovsdb_atomic_type_to_json(type->key_type); + } else { + struct json *json = json_object_create(); + json_object_put(json, "key", + ovsdb_atomic_type_to_json(type->key_type)); + if (type->value_type != OVSDB_TYPE_VOID) { + json_object_put(json, "value", + ovsdb_atomic_type_to_json(type->value_type)); + } + if (type->n_min != 1) { + json_object_put(json, "min", json_integer_create(type->n_min)); + } + if (type->n_max == UINT_MAX) { + json_object_put_string(json, "max", "unlimited"); + } else if (type->n_max != 1) { + json_object_put(json, "max", json_integer_create(type->n_max)); + } + return json; + } +} diff --git a/lib/ovsdb-types.h b/lib/ovsdb-types.h new file mode 100644 index 00000000..78d76c97 --- /dev/null +++ b/lib/ovsdb-types.h @@ -0,0 +1,126 @@ +/* Copyright (c) 2009 Nicira Networks + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OVSDB_TYPES_H +#define OVSDB_TYPES_H 1 + +#include +#include +#include "compiler.h" +#include "uuid.h" + +struct json; + +/* An atomic type: one that OVSDB regards as a single unit of data. */ +enum ovsdb_atomic_type { + OVSDB_TYPE_VOID, /* No value. */ + OVSDB_TYPE_INTEGER, /* Signed 64-bit integer. */ + OVSDB_TYPE_REAL, /* IEEE 754 double-precision floating point. */ + OVSDB_TYPE_BOOLEAN, /* True or false. */ + OVSDB_TYPE_STRING, /* UTF-8 string. */ + OVSDB_TYPE_UUID, /* RFC 4122 UUID referencing a table row. */ + OVSDB_N_TYPES +}; + +static inline bool ovsdb_atomic_type_is_valid(enum ovsdb_atomic_type); +static inline bool ovsdb_atomic_type_is_valid_key(enum ovsdb_atomic_type); +bool ovsdb_atomic_type_from_string(const char *, enum ovsdb_atomic_type *); +struct ovsdb_error *ovsdb_atomic_type_from_json(enum ovsdb_atomic_type *, + const struct json *); +const char *ovsdb_atomic_type_to_string(enum ovsdb_atomic_type); +struct json *ovsdb_atomic_type_to_json(enum ovsdb_atomic_type); + +/* An OVSDB type. One of: + * + * - An atomic type. + * + * - A set of atomic types. + * + * - A map from one atomic type to another. + */ +struct ovsdb_type { + enum ovsdb_atomic_type key_type; + enum ovsdb_atomic_type value_type; + unsigned int n_min; + unsigned int n_max; /* UINT_MAX stands in for "unlimited". */ +}; + +#define OVSDB_TYPE_SCALAR_INITIALIZER(KEY_TYPE) \ + { KEY_TYPE, OVSDB_TYPE_VOID, 1, 1 } + +extern const struct ovsdb_type ovsdb_type_integer; +extern const struct ovsdb_type ovsdb_type_real; +extern const struct ovsdb_type ovsdb_type_boolean; +extern const struct ovsdb_type ovsdb_type_string; +extern const struct ovsdb_type ovsdb_type_uuid; + +bool ovsdb_type_is_valid(const struct ovsdb_type *); + +static inline bool ovsdb_type_is_scalar(const struct ovsdb_type *); +static inline bool ovsdb_type_is_optional(const struct ovsdb_type *); +static inline bool ovsdb_type_is_composite(const struct ovsdb_type *); +static inline bool ovsdb_type_is_set(const struct ovsdb_type *); +static inline bool ovsdb_type_is_map(const struct ovsdb_type *); + +char *ovsdb_type_to_english(const struct ovsdb_type *); + +struct ovsdb_error *ovsdb_type_from_json(struct ovsdb_type *, + const struct json *) + WARN_UNUSED_RESULT; +struct json *ovsdb_type_to_json(const struct ovsdb_type *); + +/* Inline function implementations. */ + +static inline bool +ovsdb_atomic_type_is_valid(enum ovsdb_atomic_type atomic_type) +{ + return atomic_type >= 0 && atomic_type < OVSDB_N_TYPES; +} + +static inline bool +ovsdb_atomic_type_is_valid_key(enum ovsdb_atomic_type atomic_type) +{ + /* XXX should we disallow reals or booleans as keys? */ + return ovsdb_atomic_type_is_valid(atomic_type); +} + +static inline bool ovsdb_type_is_scalar(const struct ovsdb_type *type) +{ + return (type->value_type == OVSDB_TYPE_VOID + && type->n_min == 1 && type->n_max == 1); +} + +static inline bool ovsdb_type_is_optional(const struct ovsdb_type *type) +{ + return type->n_min == 0; +} + +static inline bool ovsdb_type_is_composite(const struct ovsdb_type *type) +{ + return type->n_max > 1; +} + +static inline bool ovsdb_type_is_set(const struct ovsdb_type *type) +{ + return (type->value_type == OVSDB_TYPE_VOID + && (type->n_min != 1 || type->n_max != 1)); +} + +static inline bool ovsdb_type_is_map(const struct ovsdb_type *type) +{ + return type->value_type != OVSDB_TYPE_VOID; +} + +#endif /* ovsdb-types.h */ diff --git a/lib/sort.c b/lib/sort.c new file mode 100644 index 00000000..017b0a9a --- /dev/null +++ b/lib/sort.c @@ -0,0 +1,70 @@ +/* Copyright (c) 2009 Nicira Networks + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "sort.h" + +#include "random.h" + +static size_t +partition(size_t p, size_t r, + int (*compare)(size_t a, size_t b, void *aux), + void (*swap)(size_t a, size_t b, void *aux), + void *aux) +{ + size_t x = r - 1; + size_t i, j; + + i = p; + for (j = p; j < x; j++) { + if (compare(j, x, aux) <= 0) { + swap(i++, j, aux); + } + } + swap(i, x, aux); + return i; +} + +static void +quicksort(size_t p, size_t r, + int (*compare)(size_t a, size_t b, void *aux), + void (*swap)(size_t a, size_t b, void *aux), + void *aux) +{ + size_t i, q; + + if (r - p < 2) { + return; + } + + i = random_range(r - p) + p; + if (r - 1 != i) { + swap(r - 1, i, aux); + } + + q = partition(p, r, compare, swap, aux); + quicksort(p, q, compare, swap, aux); + quicksort(q, r, compare, swap, aux); +} + +void +sort(size_t count, + int (*compare)(size_t a, size_t b, void *aux), + void (*swap)(size_t a, size_t b, void *aux), + void *aux) +{ + quicksort(0, count, compare, swap, aux); +} diff --git a/lib/sort.h b/lib/sort.h new file mode 100644 index 00000000..c952f444 --- /dev/null +++ b/lib/sort.h @@ -0,0 +1,26 @@ +/* Copyright (c) 2009 Nicira Networks + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SORT_H +#define SORT_H 1 + +#include + +void sort(size_t count, + int (*compare)(size_t a, size_t b, void *aux), + void (*swap)(size_t a, size_t b, void *aux), + void *aux); + +#endif /* sort.h */ diff --git a/lib/vlog-modules.def b/lib/vlog-modules.def index 684954ea..d5a59ab0 100644 --- a/lib/vlog-modules.def +++ b/lib/vlog-modules.def @@ -54,6 +54,11 @@ VLOG_MODULE(ofctl) VLOG_MODULE(ovs_discover) VLOG_MODULE(ofproto) VLOG_MODULE(openflowd) +VLOG_MODULE(ovsdb) +VLOG_MODULE(ovsdb_file) +VLOG_MODULE(ovsdb_jsonrpc_server) +VLOG_MODULE(ovsdb_server) +VLOG_MODULE(ovsdb_tool) VLOG_MODULE(pktbuf) VLOG_MODULE(pcap) VLOG_MODULE(poll_loop) diff --git a/ovsdb/SPECS b/ovsdb/SPECS new file mode 100644 index 00000000..12d97682 --- /dev/null +++ b/ovsdb/SPECS @@ -0,0 +1,628 @@ + =================================================== + Open vSwitch Configuration Database Specification + =================================================== + +Basic Notation +-------------- + +The descriptions below use the following shorthand notations for JSON +values. Additional notation is presented later. + + + + A JSON string. + + + + A JSON string matching [a-zA-Z_][a-zA-Z0-9_]*. + + s that begin with _ are reserved to the implementation and may + not be used by the user. + + + + A JSON true or false value. + + + + A JSON number. + + + + A JSON number with an integer value, within a certain range + (currently -2**63...+2**63-1). + +Schema Format +------------- + +An Open vSwitch configuration database consists of a set of tables, +each of which has a number of columns and zero or more rows. A schema +is represented by , as described below. + + + + A JSON object with the following members: + + "name": required + "comment": optional + "tables": {: , ...} required + + The "name" identifies the database as a whole. The "comment" + optionally provides more information about the database. The + value of "tables" is a JSON object whose names are table names and + whose values are s. + + + + A JSON object with the following members: + + "comment": optional + "columns": {: , ...} required + + The "comment" optionally provides information about this table for + a human reader. The value of "tables" is a JSON object whose + names are table names and whose values are s. + + Every table has the following columns whose definitions are not + included in the schema: + + "_uuid": This column, which contains exactly one UUID value, + is initialized to a random value by the database engine when + it creates a row. It is read-only, and its value never + changes during the lifetime of a row. + + "_version": Like "_uuid", this column contains exactly one + UUID value, initialized to a random value by the database + engine when it creates a row, and it is read-only. However, + its value changes to a new random value whenever any other + field in the row changes. Furthermore, its value is + ephemeral: when the database is closed and reopened, or when + the database process is stopped and then started again, each + "_version" also changes to a new random value. + + + + A JSON object with the following members: + + "comment": optional + "type": required + "ephemeral": optional + + The "comment" optionally provides information about this column + for a human reader. The "type" specifies the type of data stored + in this column. If "ephemeral" is specified as true, then this + column's values are not guaranteed to be durable; they may be lost + when the database restarts. + + + + The type of a database column. Either an or a JSON + object that describes the type of a database column, with the + following members: + + "key": required + "value": optional + "min": optional + "max": or "unlimited" optional + + If "min" or "max" is not specified, each defaults to 1. If "max" + is specified as "unlimited", then there is no specified maximum + number of elements, although the implementation will enforce some + limit. After considering defaults, "min" must be at least 0, + "max" must be at least 1, and "max" must be greater than or equal + to "min". + + If "min" and "max" are both 1 and "value" is not specified, the + type is the scalar type specified by "key". + + If "min" is not 1 or "max" is not 1, or both, and "value" is not + specified, the type is a set of scalar type "key". + + If "value" is specified, the type is a map from type "key" to type + "value". + + + + One of the strings "integer", "real", "boolean", "string", or + "uuid", representing the specified scalar type. + +Wire Protocol +------------- + +The database wire protocol is implemented in JSON-RPC 1.0. It +consists of the following JSON-RPC methods: + +get_schema +.......... + +Request object members: + + "method": "get_schema" required + "params": [] required + "id": any JSON value except null required + +Response object members: + + "result": + "error": null + "id": same "id" as request + +This operation retrieves a that describes the +hosted database. + +transact +........ + +Request object members: + + "method": "transact" required + "params": [*] required + "id": any JSON value except null required + +Response object members: + + "result": [*] + "error": null + "id": same "id" as request + +The "params" array for this method consists of zero or more JSON +objects, each of which represents a single database operation. The +"Operations" section below describes the valid operations. + +The value of "id" must be unique among all in-flight transactions +within the current JSON-RPC session. Otherwise, the server may return +a JSON-RPC error. + +The database server executes each of the specified operations in the +specified order, except that if an operation fails, then the remaining +operations are not executed. + +The set of operations is executed as a single atomic, consistent, +isolated transaction. The transaction is committed only if every +operation succeeds. Durability of the commit is not guaranteed unless +the "commit" operation, with "durable" set to true, is included in the +operation set (see below). + +Regardless of whether errors occur, the response is always a JSON-RPC +response with null "error" and a "result" member that is an array with +the same number of elements as "params". Each element of the "result" +array corresponds to the same element of the "params" array. The +"result" array elements may be interpreted as follows: + + - A JSON object that does not contain an "error" member indicates + that the operation completed successfully. The specific members + of the object are specified below in the descriptions of + individual operations. Some operations do not produce any + results, in which case the object will have no members. + + - A JSON object that contains a "error" member indicates that the + operation completed with an error. The value of the "error" + member is a short string, specified in this document, that + broadly indicates the class of the error. Besides the ones + listed for a specific operation, any operation may result in one + the following "error"s: + + "error": "resources exhausted" + + The operation or the transaction requires more resources + (memory, disk, CPU, etc.) than are currently available to + the database server. + + "error": "syntax error" + + The operation is not specified correctly: a required request + object member is missing, an unknown or unsupported request + object member is present, the operation attempts to act on a + table that does not exist, the operation modifies a + read-only table column, etc. + + Database implementations may use "error" strings not specified + in this document to indicate errors that do not fit into any of + the specified categories. + + Optionally, the object may include a "details" member, whose + value is a string that describes the error in more detail for + the benefit of a human user or administrator. The object may + also have other members that describe the error in more detail. + This document does not specify the names or values of these + members. + + - A JSON null value indicates that the operation was not attempted + because a prior operation failed. + +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. + +If "params" contains one or more "wait" operations, then the +transaction may take an arbitrary amount of time to complete. The +database implementation must be capable of accepting, executing, and +replying to other transactions and other JSON-RPC requests while a +transaction or transactions containing "wait" operations are +outstanding on the same or different JSON-RPC sessions. + +The section "Notation for the Wire Protocol" below describes +additional notation for use with the wire protocol. After that, the +"Operations" section describes each operation. + +cancel +...... + +Request object members: + + "method": "cancel" required + "params": the "id" for an outstanding request required + "id": null required + +Response object members: + + + +This JSON-RPC notification instructs the database server to +immediately complete or cancel the "transact" request whose "id" is +the same as the notification's "params" value. + +If the "transact" request can be completed immediately, then the +server sends a response in the form described for "transact", above. +Otherwise, the server sends a JSON-RPC error response of the following +form: + + "result": null + "error": "canceled" + "id": the request "id" member + +The "cancel" notification itself has no reply. + +Notation for the Wire Protocol +------------------------------ + + + + An that names a table. + + + + An that names a table column. + + + + A JSON object that describes a table row or a subset of a table + row. Each member is the name of a table column paired with the + of that column. + + + + A JSON value that represents the value of a column in a table row, + one of , a , or a . + + + + A JSON value that represents a scalar value for a column, one of + , , , , . + + + + A 2-element JSON array that represents a database set value. The + first element of the array must be the string "set" and the second + element must be an array of zero or more s giving the values + in the set. All of the s must have the same type. + + + + A 2-element JSON array that represents a database map value. The + first element of the array must be the string "map" and the second + element must be an array of zero or more s giving the values + in the map. All of the s must have the same key and value + types. + + (JSON objects are not used to represent because JSON only + allows string names in an object.) + + + + A 2-element JSON array that represents a pair within a database + map. The first element is an that represents the key, the + second element is an that represents the value. + + + + A 2-element JSON array that represents a UUID. The first element + of the array must be the string "uuid" and the second element must + be a 36-character string giving the UUID in the format described + by RFC 4122. For example, the following represents the + UUID 550e8400-e29b-41d4-a716-446655440000: + + ["uuid", "550e8400-e29b-41d4-a716-446655440000"] + + + + A 2-element JSON array that represents the UUID of a row inserted + in a previous "insert" operation within the same transaction. The + first element of the array must be the string "named-uuid" and the + second element must be the string specified on a previous "insert" + operation's "uuid-name". For example, if a previous "insert" + operation specified a "uuid-name" of "myrow", the following + represents the UUID created by that operation: + + ["named-uuid", "myrow"] + + + + A 3-element JSON array of the form [, , + ] that represents a test on a column value. + + Except as otherwise specified below, must have the same + type as . + + The meaning depends on the type of : + + integer + real + + must be "<", "<=", "==", "!=", ">=", ">", + "includes", or "excludes". + + The test is true if the column's value satisfies the + relation , e.g. if the column has value + 1 and is 2, the test is true if is "<", + "<=" or "!=", but not otherwise. + + "includes" is equivalent to "=="; "excludes" is equivalent + to "!=". + + boolean + string + uuid + + must be "!=", "==", "includes", or "excludes". + + If is "==" or "includes", the test is true if + the column's value equals . If is "!=" + or "excludes", the test is inverted. + + set + map + + must be "!=", "==", "includes", or "excludes". + + If is "==", the test is true if the column's + value contains exactly the same values (for sets) or pairs + (for maps). If is "!=", the test is inverted. + + If is "includes", the test is true if the + column's value contains all of the values (for sets) or + pairs (for maps) in . The column's value may also + contain other values or pairs. + + If is "excludes", the test is true if the + column's value does not contain any of the values (for + sets) or pairs (for maps) in . The column's value + may contain other values or pairs not in . + + If is "includes" or "excludes", then the + required type of is slightly relaxed, in that it + may have fewer than the minimum number of elements + specified by the column's type. If is + "excludes", then the required type is additionally relaxed + in that may have more than the maximum number of + elements specified by the column's type. + + + + One of "<", "<=", "==", "!=", ">=", ">", "includes", "excludes". + +Operations +---------- + +Each of the available operations is described below. + +insert +...... + +Request object members: + + "op": "insert" required + "table":
required + "row": required + "uuid-name": optional + +Result object members: + + "uuid": + +Semantics: + + Inserts "row" into "table". If "row" does not specify values + for all the columns in "table", those columns receive default + values. + + The new row receives a new, randomly generated UUID, which is + returned as the "_uuid" member of the result. If "uuid-name" + is supplied, then the UUID is made available under that name + to later operations within the same transaction. + +select +...... + +Request object members: + + "op": "select" required + "table":
required + "where": [*] required + "columns": [*] optional + +Result object members: + + "rows": [*] + +Semantics: + + Searches "table" for rows that match all the conditions specified + in "where". If "where" is an empty array, every row in "table" is + selected. + + The "rows" member of the result is an array of objects. Each + object corresponds to a matching row, with each column + specified in "columns" as a member, the column's name as the + member name and its value as the member value. If "columns" + is not specified, all the table's columns are included. If + two rows of the result have the same values for all included + columns, only one copy of that row is included in "rows". + Specifying "_uuid" within "columns" will avoid dropping + duplicates, since every row has a unique UUID. + + The ordering of rows within "rows" is unspecified. + +update +...... + +Request object members: + + "op": "update" required + "table":
required + "where": [*] required + "row": required + +Result object members: + + "count": + +Semantics: + + Updates rows in a table. + + Searches "table" for rows that match all the conditions + specified in "where". For each matching row, changes the + value of each column specified in "row" to the value for that + column specified in "row". + + The "_uuid" and "_version" columns of a table may not be updated. + Columns designated read-only in the schema also may not be + updated. + + The "count" member of the result specifies the number of rows + that matched. + +delete +...... + +Request object members: + + "op": "delete" required + "table":
required + "where": [*] required + +Result object members: + + "count": + +Semantics: + + Deletes all the rows from "table" that match all the conditions + specified in "where". + + The "count" member of the result specifies the number of deleted + rows. + +wait +.... + +Request object members: + + "op": "wait" required + "timeout": optional + "table":
required + "where": [*] required + "columns": [*] required + "until": "==" or "!=" required + "rows": [*] required + +Result object members: + + none + +Semantics: + + Waits until a condition becomes true. + + If "until" is "==", checks whether the query on "table" specified + by "where" and "columns", which is evaluated in the same way as + specified for "select", returns the result set specified by + "rows". If it does, then the operation completes successfully. + Otherwise, the entire transaction rolls back. It is automatically + restarted later, after a change in the database makes it possible + for the operation to succeed. The client will not receive a + response until the operation permanently succeeds or fails. + + If "until" is "!=", the sense of the test is negated. That is, as + long as the query on "table" specified by "where" and "columns" + returns "rows", the transaction will be rolled back and restarted + later. + + If "timeout" is specified, then the transaction aborts after the + specified number of milliseconds. The transaction is guaranteed + to be attempted at least once before it aborts. A "timeout" of 0 + will abort the transaction on the first mismatch. + +Errors: + + "error": "not supported" + + One or more of the columns in this table do not support + triggers. This error will not occur if "timeout" is 0. + + "error": "timed out" + + The "timeout" was reached before the transaction was able to + complete. + +commit +...... + +Request object members: + + "op": "commit" required + "durable": required + +Result object members: + + none + +Semantics: + + If "durable" is specified as true, then the transaction, if it + commits, will be stored durably (to disk) before the reply is sent + to the client. + +Errors: + + "error": "not supported" + + When "durable" is true, this database implementation does not + support durable commits. + +abort +..... + +Request object members: + + "op": "abort" required + +Result object members: + + (never succeeds) + +Semantics: + + Aborts the transaction with an error. This may be useful for + testing. + +Errors: + + "error": "aborted" + + This operation always fails with this error. diff --git a/ovsdb/automake.mk b/ovsdb/automake.mk new file mode 100644 index 00000000..d2a3e04c --- /dev/null +++ b/ovsdb/automake.mk @@ -0,0 +1,44 @@ +# libovsdb +noinst_LIBRARIES += ovsdb/libovsdb.a +ovsdb_libovsdb_a_SOURCES = \ + ovsdb/column.c \ + ovsdb/column.h \ + ovsdb/condition.c \ + ovsdb/condition.h \ + ovsdb/execution.c \ + ovsdb/file.c \ + ovsdb/file.h \ + ovsdb/jsonrpc-server.c \ + ovsdb/jsonrpc-server.h \ + ovsdb/ovsdb-server.c \ + ovsdb/ovsdb.c \ + ovsdb/ovsdb.h \ + ovsdb/query.c \ + ovsdb/query.h \ + ovsdb/row.c \ + ovsdb/row.h \ + ovsdb/table.c \ + ovsdb/table.h \ + ovsdb/trigger.c \ + ovsdb/trigger.h \ + ovsdb/transaction.c \ + ovsdb/transaction.h + +# ovsdb-tool +bin_PROGRAMS += ovsdb/ovsdb-tool +ovsdb_ovsdb_tool_SOURCES = ovsdb/ovsdb-tool.c +ovsdb_ovsdb_tool_LDADD = ovsdb/libovsdb.a lib/libopenvswitch.a + +## ovsdb-tool.8 +#man_MANS += ovsdb/ovsdb-tool.8 +#DISTCLEANFILES += ovsdb/ovsdb-tool.8 +#EXTRA_DIST += ovsdb/ovsdb-tool.8.in + +# ovsdb-server +sbin_PROGRAMS += ovsdb/ovsdb-server +ovsdb_ovsdb_server_SOURCES = ovsdb/ovsdb-server.c +ovsdb_ovsdb_server_LDADD = ovsdb/libovsdb.a lib/libopenvswitch.a $(FAULT_LIBS) +## ovsdb-server.8 +#man_MANS += ovsdb/ovsdb-server.8 +#DISTCLEANFILES += ovsdb/ovsdb-server.8 +#EXTRA_DIST += ovsdb/ovsdb-server.8.in diff --git a/ovsdb/column.c b/ovsdb/column.c new file mode 100644 index 00000000..1e8a2d09 --- /dev/null +++ b/ovsdb/column.c @@ -0,0 +1,232 @@ +/* Copyright (c) 2009 Nicira Networks + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "ovsdb/column.h" + +#include + +#include "column.h" +#include "json.h" +#include "ovsdb-error.h" +#include "ovsdb-parser.h" +#include "table.h" +#include "util.h" + +struct ovsdb_column * +ovsdb_column_create(const char *name, const char *comment, + bool mutable, bool persistent, + const struct ovsdb_type *type) +{ + struct ovsdb_column *ts; + + ts = xzalloc(sizeof *ts); + ts->name = xstrdup(name); + ts->comment = comment ? xstrdup(comment) : NULL; + ts->mutable = mutable; + ts->persistent = persistent; + ts->type = *type; + + return ts; +} + +void +ovsdb_column_destroy(struct ovsdb_column *column) +{ + free(column->name); + free(column->comment); + free(column); +} + +struct ovsdb_error * +ovsdb_column_from_json(const struct json *json, const char *name, + struct ovsdb_column **columnp) +{ + const struct json *comment, *mutable, *ephemeral, *type_json; + struct ovsdb_error *error; + struct ovsdb_type type; + struct ovsdb_parser parser; + bool persistent; + + *columnp = NULL; + + ovsdb_parser_init(&parser, json, "schema for column %s", name); + comment = ovsdb_parser_member(&parser, "comment", OP_STRING | OP_OPTIONAL); + mutable = ovsdb_parser_member(&parser, "mutable", + OP_TRUE | OP_FALSE | OP_OPTIONAL); + ephemeral = ovsdb_parser_member(&parser, "ephemeral", + OP_TRUE | OP_FALSE | OP_OPTIONAL); + type_json = ovsdb_parser_member(&parser, "type", OP_STRING | OP_OBJECT); + error = ovsdb_parser_finish(&parser); + if (error) { + return error; + } + + error = ovsdb_type_from_json(&type, type_json); + if (error) { + return error; + } + + persistent = ephemeral ? !json_boolean(ephemeral) : true; + *columnp = ovsdb_column_create(name, + comment ? json_string(comment) : NULL, + mutable ? json_boolean(mutable) : true, + persistent, &type); + return NULL; +} + +struct json * +ovsdb_column_to_json(const struct ovsdb_column *column) +{ + struct json *json = json_object_create(); + if (column->comment) { + json_object_put_string(json, "comment", column->comment); + } + if (!column->mutable) { + json_object_put(json, "mutable", json_boolean_create(false)); + } + if (!column->persistent) { + json_object_put(json, "ephemeral", json_boolean_create(true)); + } + json_object_put(json, "type", ovsdb_type_to_json(&column->type)); + return json; +} + +void +ovsdb_column_set_init(struct ovsdb_column_set *set) +{ + set->columns = NULL; + set->n_columns = set->allocated_columns = 0; +} + +void +ovsdb_column_set_destroy(struct ovsdb_column_set *set) +{ + free(set->columns); +} + +void +ovsdb_column_set_clone(struct ovsdb_column_set *new, + const struct ovsdb_column_set *old) +{ + new->columns = xmemdup(old->columns, + old->n_columns * sizeof *old->columns); + new->n_columns = new->allocated_columns = old->n_columns; +} + +struct ovsdb_error * +ovsdb_column_set_from_json(const struct json *json, + const struct ovsdb_table *table, + struct ovsdb_column_set *set) +{ + ovsdb_column_set_init(set); + if (!json) { + struct shash_node *node; + + SHASH_FOR_EACH (node, &table->schema->columns) { + const struct ovsdb_column *column = node->data; + ovsdb_column_set_add(set, column); + } + + return NULL; + } else { + size_t i; + + if (json->type != JSON_ARRAY) { + goto error; + } + + /* XXX this is O(n**2) */ + for (i = 0; i < json->u.array.n; i++) { + struct ovsdb_column *column; + + if (json->u.array.elems[i]->type != JSON_STRING) { + goto error; + } + + column = shash_find_data(&table->schema->columns, + json->u.array.elems[i]->u.string); + if (ovsdb_column_set_contains(set, column->index)) { + goto error; + } + ovsdb_column_set_add(set, column); + } + + return NULL; + } + +error: + ovsdb_column_set_destroy(set); + return ovsdb_syntax_error(json, NULL, + "array of distinct column names expected"); +} + +void +ovsdb_column_set_add(struct ovsdb_column_set *set, + const struct ovsdb_column *column) +{ + if (set->n_columns >= set->allocated_columns) { + set->columns = x2nrealloc(set->columns, &set->allocated_columns, + sizeof *set->columns); + } + set->columns[set->n_columns++] = column; +} + +void +ovsdb_column_set_add_all(struct ovsdb_column_set *set, + const struct ovsdb_table *table) +{ + struct shash_node *node; + + SHASH_FOR_EACH (node, &table->schema->columns) { + const struct ovsdb_column *column = node->data; + ovsdb_column_set_add(set, column); + } +} + +bool +ovsdb_column_set_contains(const struct ovsdb_column_set *set, + unsigned int column_index) +{ + size_t i; + + for (i = 0; i < set->n_columns; i++) { + if (set->columns[i]->index == column_index) { + return true; + } + } + return false; +} + +/* This comparison is sensitive to ordering of columns within a set, but that's + * good: the only existing caller wants to make sure that hash values are + * comparable, which is only true if column ordering is the same. */ +bool +ovsdb_column_set_equals(const struct ovsdb_column_set *a, + const struct ovsdb_column_set *b) +{ + size_t i; + + if (a->n_columns != b->n_columns) { + return false; + } + for (i = 0; i < a->n_columns; i++) { + if (a->columns[i] != b->columns[i]) { + return false; + } + } + return true; +} diff --git a/ovsdb/column.h b/ovsdb/column.h new file mode 100644 index 00000000..59421510 --- /dev/null +++ b/ovsdb/column.h @@ -0,0 +1,84 @@ +/* Copyright (c) 2009 Nicira Networks + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OVSDB_COLUMN_H +#define OVSDB_COLUMN_H 1 + +#include +#include "compiler.h" +#include "ovsdb-types.h" + +struct ovsdb_table; + +/* A column or a column schema (currently there is no distinction). */ +struct ovsdb_column { + unsigned int index; + char *name; + + char *comment; + bool mutable; + bool persistent; + struct ovsdb_type type; +}; + +/* A few columns appear in every table with standardized column indexes. + * These macros define those columns' indexes. + * + * Don't change these values, because ovsdb_query() depends on OVSDB_COL_UUID + * having value 0. */ +enum { + OVSDB_COL_UUID = 0, /* UUID for the row. */ + OVSDB_COL_VERSION = 1, /* Version number for the row. */ + OVSDB_N_STD_COLUMNS +}; + +struct ovsdb_column *ovsdb_column_create( + const char *name, const char *comment, bool mutable, bool persistent, + const struct ovsdb_type *); +void ovsdb_column_destroy(struct ovsdb_column *); + +struct ovsdb_error *ovsdb_column_from_json(const struct json *, + const char *name, + struct ovsdb_column **) + WARN_UNUSED_RESULT; +struct json *ovsdb_column_to_json(const struct ovsdb_column *); + +/* An unordered set of distinct columns. */ + +struct ovsdb_column_set { + const struct ovsdb_column **columns; + size_t n_columns, allocated_columns; +}; + +#define OVSDB_COLUMN_SET_INITIALIZER { NULL, 0, 0 } + +void ovsdb_column_set_init(struct ovsdb_column_set *); +void ovsdb_column_set_destroy(struct ovsdb_column_set *); +void ovsdb_column_set_clone(struct ovsdb_column_set *, + const struct ovsdb_column_set *); +struct ovsdb_error *ovsdb_column_set_from_json(const struct json *, + const struct ovsdb_table *, + struct ovsdb_column_set *); + +void ovsdb_column_set_add(struct ovsdb_column_set *, + const struct ovsdb_column *); +void ovsdb_column_set_add_all(struct ovsdb_column_set *, + const struct ovsdb_table *); +bool ovsdb_column_set_contains(const struct ovsdb_column_set *, + unsigned int column_index); +bool ovsdb_column_set_equals(const struct ovsdb_column_set *, + const struct ovsdb_column_set *); + +#endif /* column.h */ diff --git a/ovsdb/condition.c b/ovsdb/condition.c new file mode 100644 index 00000000..0342b8e8 --- /dev/null +++ b/ovsdb/condition.c @@ -0,0 +1,284 @@ +/* Copyright (c) 2009 Nicira Networks + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "condition.h" + +#include + +#include "column.h" +#include "json.h" +#include "ovsdb-error.h" +#include "row.h" +#include "table.h" + +struct ovsdb_error * +ovsdb_function_from_string(const char *name, enum ovsdb_function *function) +{ +#define OVSDB_FUNCTION(ENUM, NAME) \ + if (!strcmp(name, NAME)) { \ + *function = ENUM; \ + return NULL; \ + } + OVSDB_FUNCTIONS; +#undef OVSDB_FUNCTION + + return ovsdb_syntax_error(NULL, "unknown function", + "No function named %s.", name); +} + +const char * +ovsdb_function_to_string(enum ovsdb_function function) +{ + switch (function) { +#define OVSDB_FUNCTION(ENUM, NAME) case ENUM: return NAME; + OVSDB_FUNCTIONS; +#undef OVSDB_FUNCTION + } + + return NULL; +} + + +static WARN_UNUSED_RESULT struct ovsdb_error * +ovsdb_clause_from_json(const struct ovsdb_table_schema *ts, + const struct json *json, + const struct ovsdb_symbol_table *symtab, + struct ovsdb_clause *clause) +{ + const struct json_array *array; + struct ovsdb_error *error; + const char *function_name; + const char *column_name; + struct ovsdb_type type; + + if (json->type != JSON_ARRAY + || json->u.array.n != 3 + || json->u.array.elems[0]->type != JSON_STRING + || json->u.array.elems[1]->type != JSON_STRING) { + return ovsdb_syntax_error(json, NULL, "Parse error in condition."); + } + array = json_array(json); + + column_name = json_string(array->elems[0]); + clause->column = ovsdb_table_schema_get_column(ts, column_name); + if (!clause->column) { + return ovsdb_syntax_error(json, "unknown column", + "No column %s in table %s.", + column_name, ts->name); + } + type = clause->column->type; + + function_name = json_string(array->elems[1]); + error = ovsdb_function_from_string(function_name, &clause->function); + if (error) { + return error; + } + + /* Type-check and relax restrictions on 'type' if appropriate. */ + switch (clause->function) { + case OVSDB_F_LT: + case OVSDB_F_LE: + case OVSDB_F_GT: + case OVSDB_F_GE: + /* XXX should we also allow these operators for types with n_min == 0, + * n_max == 1? (They would always be "false" if the value was + * missing.) */ + if (!ovsdb_type_is_scalar(&type) + || (type.key_type != OVSDB_TYPE_INTEGER + && type.key_type != OVSDB_TYPE_REAL)) { + char *s = ovsdb_type_to_english(&type); + error = ovsdb_syntax_error( + json, NULL, "Type mismatch: \"%s\" operator may not be " + "applied to column %s of type %s.", + ovsdb_function_to_string(clause->function), + clause->column->name, s); + free(s); + return error; + } + break; + + case OVSDB_F_EQ: + case OVSDB_F_NE: + break; + + case OVSDB_F_EXCLUDES: + if (!ovsdb_type_is_scalar(&type)) { + type.n_min = 0; + type.n_max = UINT_MAX; + } + break; + + case OVSDB_F_INCLUDES: + if (!ovsdb_type_is_scalar(&type)) { + type.n_min = 0; + } + break; + } + return ovsdb_datum_from_json(&clause->arg, &type, array->elems[2], symtab); +} + +static void +ovsdb_clause_free(struct ovsdb_clause *clause) +{ + ovsdb_datum_destroy(&clause->arg, &clause->column->type); +} + +static int +compare_clauses_3way(const void *a_, const void *b_) +{ + const struct ovsdb_clause *a = a_; + const struct ovsdb_clause *b = b_; + + if (a->function != b->function) { + /* Bring functions to the front based on the fraction of table rows + * that they are (heuristically) expected to leave in the query + * results. Note that "enum ovsdb_function" is intentionally ordered + * to make this trivial. */ + return a->function < b->function ? -1 : 1; + } else if (a->column->index != b->column->index) { + if (a->column->index < OVSDB_N_STD_COLUMNS + || b->column->index < OVSDB_N_STD_COLUMNS) { + /* Bring the standard columns and in particular the UUID column + * (since OVSDB_COL_UUID has value 0) to the front. We have an + * index on the UUID column, so that makes our queries cheaper. */ + return a->column->index < b->column->index ? -1 : 1; + } else { + /* Order clauses predictably to make testing easier. */ + return strcmp(a->column->name, b->column->name); + } + } else { + return 0; + } +} + +struct ovsdb_error * +ovsdb_condition_from_json(const struct ovsdb_table_schema *ts, + const struct json *json, + const struct ovsdb_symbol_table *symtab, + struct ovsdb_condition *cnd) +{ + const struct json_array *array = json_array(json); + size_t i; + + cnd->clauses = xmalloc(array->n * sizeof *cnd->clauses); + cnd->n_clauses = 0; + for (i = 0; i < array->n; i++) { + struct ovsdb_error *error; + error = ovsdb_clause_from_json(ts, array->elems[i], symtab, + &cnd->clauses[i]); + if (error) { + ovsdb_condition_destroy(cnd); + cnd->clauses = NULL; + cnd->n_clauses = 0; + return error; + } + cnd->n_clauses++; + } + + /* A real database would have a query optimizer here. */ + qsort(cnd->clauses, cnd->n_clauses, sizeof *cnd->clauses, + compare_clauses_3way); + + return NULL; +} + +static struct json * +ovsdb_clause_to_json(const struct ovsdb_clause *clause) +{ + return json_array_create_3( + json_string_create(clause->column->name), + json_string_create(ovsdb_function_to_string(clause->function)), + ovsdb_datum_to_json(&clause->arg, &clause->column->type)); +} + +struct json * +ovsdb_condition_to_json(const struct ovsdb_condition *cnd) +{ + struct json **clauses; + size_t i; + + clauses = xmalloc(cnd->n_clauses * sizeof *clauses); + for (i = 0; i < cnd->n_clauses; i++) { + clauses[i] = ovsdb_clause_to_json(&cnd->clauses[i]); + } + return json_array_create(clauses, cnd->n_clauses); +} + +bool +ovsdb_condition_evaluate(const struct ovsdb_row *row, + const struct ovsdb_condition *cnd) +{ + size_t i; + + for (i = 0; i < cnd->n_clauses; i++) { + const struct ovsdb_clause *c = &cnd->clauses[i]; + const struct ovsdb_datum *field = &row->fields[c->column->index]; + const struct ovsdb_datum *arg = &cnd->clauses[i].arg; + const struct ovsdb_type *type = &c->column->type; + + if (ovsdb_type_is_scalar(type)) { + int cmp = ovsdb_atom_compare_3way(&field->keys[0], &arg->keys[0], + type->key_type); + switch (c->function) { + case OVSDB_F_LT: + return cmp < 0; + case OVSDB_F_LE: + return cmp <= 0; + case OVSDB_F_EQ: + case OVSDB_F_INCLUDES: + return cmp == 0; + case OVSDB_F_NE: + case OVSDB_F_EXCLUDES: + return cmp != 0; + case OVSDB_F_GE: + return cmp >= 0; + case OVSDB_F_GT: + return cmp > 0; + } + } else { + switch (c->function) { + case OVSDB_F_EQ: + return ovsdb_datum_equals(field, arg, type); + case OVSDB_F_NE: + return !ovsdb_datum_equals(field, arg, type); + case OVSDB_F_INCLUDES: + return ovsdb_datum_includes_all(arg, field, type); + case OVSDB_F_EXCLUDES: + return ovsdb_datum_excludes_all(arg, field, type); + case OVSDB_F_LT: + case OVSDB_F_LE: + case OVSDB_F_GE: + case OVSDB_F_GT: + NOT_REACHED(); + } + } + NOT_REACHED(); + } + + return true; +} + +void +ovsdb_condition_destroy(struct ovsdb_condition *cnd) +{ + size_t i; + + for (i = 0; i < cnd->n_clauses; i++) { + ovsdb_clause_free(&cnd->clauses[i]); + } + free(cnd->clauses); +} diff --git a/ovsdb/condition.h b/ovsdb/condition.h new file mode 100644 index 00000000..8c422b95 --- /dev/null +++ b/ovsdb/condition.h @@ -0,0 +1,72 @@ +/* Copyright (c) 2009 Nicira Networks + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OVSDB_CONDITION_H +#define OVSDB_CONDITION_H 1 + +#include +#include "compiler.h" +#include "ovsdb-data.h" + +struct json; +struct ovsdb_table_schema; +struct ovsdb_row; + +/* These list is ordered in ascending order of the fraction of tables row that + * they are (heuristically) expected to leave in query results. */ +#define OVSDB_FUNCTIONS \ + OVSDB_FUNCTION(OVSDB_F_EQ, "==") \ + OVSDB_FUNCTION(OVSDB_F_INCLUDES, "includes") \ + OVSDB_FUNCTION(OVSDB_F_LE, "<=") \ + OVSDB_FUNCTION(OVSDB_F_LT, "<") \ + OVSDB_FUNCTION(OVSDB_F_GE, ">=") \ + OVSDB_FUNCTION(OVSDB_F_GT, ">") \ + OVSDB_FUNCTION(OVSDB_F_EXCLUDES, "excludes") \ + OVSDB_FUNCTION(OVSDB_F_NE, "!=") + +enum ovsdb_function { +#define OVSDB_FUNCTION(ENUM, NAME) ENUM, + OVSDB_FUNCTIONS +#undef OVSDB_FUNCTION +}; + +struct ovsdb_error *ovsdb_function_from_string(const char *, + enum ovsdb_function *) + WARN_UNUSED_RESULT; +const char *ovsdb_function_to_string(enum ovsdb_function); + +struct ovsdb_clause { + enum ovsdb_function function; + const struct ovsdb_column *column; + struct ovsdb_datum arg; +}; + +struct ovsdb_condition { + struct ovsdb_clause *clauses; + size_t n_clauses; +}; + +#define OVSDB_CONDITION_INITIALIZER { NULL, 0 } + +struct ovsdb_error *ovsdb_condition_from_json( + const struct ovsdb_table_schema *, + const struct json *, const struct ovsdb_symbol_table *, + struct ovsdb_condition *) WARN_UNUSED_RESULT; +struct json *ovsdb_condition_to_json(const struct ovsdb_condition *); +void ovsdb_condition_destroy(struct ovsdb_condition *); +bool ovsdb_condition_evaluate(const struct ovsdb_row *, + const struct ovsdb_condition *); + +#endif /* ovsdb/condition.h */ diff --git a/ovsdb/execution.c b/ovsdb/execution.c new file mode 100644 index 00000000..25b34b19 --- /dev/null +++ b/ovsdb/execution.c @@ -0,0 +1,613 @@ +/* Copyright (c) 2009 Nicira Networks + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include + +#include "column.h" +#include "condition.h" +#include "file.h" +#include "json.h" +#include "ovsdb-data.h" +#include "ovsdb-error.h" +#include "ovsdb-parser.h" +#include "ovsdb.h" +#include "query.h" +#include "row.h" +#include "table.h" +#include "timeval.h" +#include "transaction.h" + +struct ovsdb_execution { + struct ovsdb *db; + struct ovsdb_txn *txn; + struct ovsdb_symbol_table *symtab; + bool durable; + + /* Triggers. */ + long long int elapsed_msec; + long long int timeout_msec; +}; + +typedef struct ovsdb_error *ovsdb_operation_executor(struct ovsdb_execution *, + struct ovsdb_parser *, + struct json *result); + +static struct ovsdb_error *do_commit(struct ovsdb_execution *); +static ovsdb_operation_executor ovsdb_execute_insert; +static ovsdb_operation_executor ovsdb_execute_select; +static ovsdb_operation_executor ovsdb_execute_update; +static ovsdb_operation_executor ovsdb_execute_delete; +static ovsdb_operation_executor ovsdb_execute_wait; +static ovsdb_operation_executor ovsdb_execute_commit; +static ovsdb_operation_executor ovsdb_execute_abort; + +static ovsdb_operation_executor * +lookup_executor(const char *name) +{ + struct ovsdb_operation { + const char *name; + ovsdb_operation_executor *executor; + }; + + static const struct ovsdb_operation operations[] = { + { "insert", ovsdb_execute_insert }, + { "select", ovsdb_execute_select }, + { "update", ovsdb_execute_update }, + { "delete", ovsdb_execute_delete }, + { "wait", ovsdb_execute_wait }, + { "commit", ovsdb_execute_commit }, + { "abort", ovsdb_execute_abort }, + }; + + size_t i; + + for (i = 0; i < ARRAY_SIZE(operations); i++) { + const struct ovsdb_operation *c = &operations[i]; + if (!strcmp(c->name, name)) { + return c->executor; + } + } + return NULL; +} + +struct json * +ovsdb_execute(struct ovsdb *db, const struct json *params, + long long int elapsed_msec, long long int *timeout_msec) +{ + struct ovsdb_execution x; + struct ovsdb_error *error; + struct json *results; + size_t n_operations; + size_t i; + + if (params->type != JSON_ARRAY) { + struct ovsdb_error *error; + + error = ovsdb_syntax_error(params, NULL, "array expected"); + results = ovsdb_error_to_json(error); + ovsdb_error_destroy(error); + return results; + } + + x.db = db; + x.txn = ovsdb_txn_create(db); + x.symtab = ovsdb_symbol_table_create(); + x.durable = false; + x.elapsed_msec = elapsed_msec; + x.timeout_msec = LLONG_MAX; + results = NULL; + + results = json_array_create_empty(); + n_operations = params->u.array.n; + error = NULL; + for (i = 0; i < n_operations; i++) { + struct json *operation = params->u.array.elems[i]; + struct ovsdb_error *parse_error; + struct ovsdb_parser parser; + struct json *result; + const struct json *op; + + /* Parse and execute operation. */ + ovsdb_parser_init(&parser, operation, + "ovsdb operation %zu of %zu", i + 1, n_operations); + op = ovsdb_parser_member(&parser, "op", OP_ID); + result = json_object_create(); + if (op) { + const char *op_name = json_string(op); + ovsdb_operation_executor *executor = lookup_executor(op_name); + if (executor) { + error = executor(&x, &parser, result); + } else { + error = ovsdb_syntax_error(operation, "unknown operation", + "No operation \"%s\"", op_name); + } + } else { + assert(ovsdb_parser_has_error(&parser)); + } + + /* A parse error overrides any other error. + * An error overrides any other result. */ + parse_error = ovsdb_parser_finish(&parser); + if (parse_error) { + ovsdb_error_destroy(error); + error = parse_error; + } + if (error) { + json_destroy(result); + result = ovsdb_error_to_json(error); + } + if (error && !strcmp(ovsdb_error_get_tag(error), "not supported") + && timeout_msec) { + ovsdb_txn_abort(x.txn); + *timeout_msec = x.timeout_msec; + ovsdb_error_destroy(error); + json_destroy(results); + return NULL; + } + + /* Add result to array. */ + json_array_add(results, result); + if (error) { + break; + } + } + + if (!error) { + /* Commit transaction. Bail if commit encounters error. */ + error = do_commit(&x); + if (error) { + json_array_add(results, ovsdb_error_to_json(error)); + } + } else { + ovsdb_txn_abort(x.txn); + } + + while (json_array(results)->n < n_operations) { + json_array_add(results, json_null_create()); + } + + ovsdb_error_destroy(error); + ovsdb_symbol_table_destroy(x.symtab); + + return results; +} + +struct ovsdb_error * +ovsdb_execute_commit(struct ovsdb_execution *x, struct ovsdb_parser *parser, + struct json *result UNUSED) +{ + const struct json *durable; + + durable = ovsdb_parser_member(parser, "durable", OP_BOOLEAN); + if (durable && json_boolean(durable)) { + x->durable = true; + } + return NULL; +} + +static struct ovsdb_error * +ovsdb_execute_abort(struct ovsdb_execution *x UNUSED, + struct ovsdb_parser *parser UNUSED, + struct json *result UNUSED) +{ + return ovsdb_error("aborted", "aborted by request"); +} + +static struct ovsdb_error * +do_commit(struct ovsdb_execution *x) +{ + if (x->db->file) { + struct ovsdb_error *error; + struct json *json; + + json = ovsdb_txn_to_json(x->txn); + if (!json) { + /* Nothing to commit. */ + return NULL; + } + + error = ovsdb_file_write(x->db->file, json); + json_destroy(json); + if (error) { + return ovsdb_wrap_error(error, "writing transaction failed"); + } + + if (x->durable) { + error = ovsdb_file_commit(x->db->file); + if (error) { + return ovsdb_wrap_error(error, + "committing transaction failed"); + } + } + } + + ovsdb_txn_commit(x->txn); + return NULL; +} + +static struct ovsdb_table * +parse_table(struct ovsdb_execution *x, + struct ovsdb_parser *parser, const char *member) +{ + struct ovsdb_table *table; + const char *table_name; + const struct json *json; + + json = ovsdb_parser_member(parser, member, OP_ID); + if (!json) { + return NULL; + } + table_name = json_string(json); + + table = shash_find_data(&x->db->tables, table_name); + if (!table) { + ovsdb_parser_raise_error(parser, "No table named %s.", table_name); + } + return table; +} + +static WARN_UNUSED_RESULT struct ovsdb_error * +parse_row(struct ovsdb_parser *parser, const char *member, + const struct ovsdb_table *table, + const struct ovsdb_symbol_table *symtab, + struct ovsdb_row **rowp, struct ovsdb_column_set *columns) +{ + struct ovsdb_error *error; + const struct json *json; + struct ovsdb_row *row; + + *rowp = NULL; + + if (!table) { + return OVSDB_BUG("null table"); + } + json = ovsdb_parser_member(parser, member, OP_OBJECT); + if (!json) { + return OVSDB_BUG("null row member"); + } + + row = ovsdb_row_create(table); + error = ovsdb_row_from_json(row, json, symtab, columns); + if (error) { + ovsdb_row_destroy(row); + return error; + } else { + *rowp = row; + return NULL; + } +} + +struct ovsdb_error * +ovsdb_execute_insert(struct ovsdb_execution *x, struct ovsdb_parser *parser, + struct json *result) +{ + struct ovsdb_table *table; + struct ovsdb_row *row = NULL; + const struct json *uuid_name; + struct ovsdb_error *error; + + table = parse_table(x, parser, "table"); + uuid_name = ovsdb_parser_member(parser, "uuid-name", OP_ID | OP_OPTIONAL); + error = ovsdb_parser_get_error(parser); + if (!error) { + error = parse_row(parser, "row", table, x->symtab, &row, NULL); + } + if (!error) { + uuid_generate(ovsdb_row_get_uuid_rw(row)); + if (uuid_name) { + ovsdb_symbol_table_put(x->symtab, json_string(uuid_name), + ovsdb_row_get_uuid(row)); + } + ovsdb_txn_row_insert(x->txn, row); + json_object_put(result, "uuid", + ovsdb_datum_to_json(&row->fields[OVSDB_COL_UUID], + &ovsdb_type_uuid)); + row = NULL; + } + return error; +} + +struct ovsdb_error * +ovsdb_execute_select(struct ovsdb_execution *x, struct ovsdb_parser *parser, + struct json *result) +{ + struct ovsdb_table *table; + const struct json *where, *columns_json, *sort_json; + struct ovsdb_condition condition = OVSDB_CONDITION_INITIALIZER; + struct ovsdb_column_set columns = OVSDB_COLUMN_SET_INITIALIZER; + struct ovsdb_column_set sort = OVSDB_COLUMN_SET_INITIALIZER; + struct ovsdb_error *error; + + table = parse_table(x, parser, "table"); + where = ovsdb_parser_member(parser, "where", OP_ARRAY); + columns_json = ovsdb_parser_member(parser, "columns", + OP_ARRAY | OP_OPTIONAL); + sort_json = ovsdb_parser_member(parser, "sort", OP_ARRAY | OP_OPTIONAL); + + error = ovsdb_parser_get_error(parser); + if (!error) { + error = ovsdb_condition_from_json(table->schema, where, x->symtab, + &condition); + } + if (!error) { + error = ovsdb_column_set_from_json(columns_json, table, &columns); + } + if (!error) { + error = ovsdb_column_set_from_json(sort_json, table, &sort); + } + if (!error) { + struct ovsdb_row_set rows = OVSDB_ROW_SET_INITIALIZER; + + ovsdb_query_distinct(table, &condition, &columns, &rows); + ovsdb_row_set_sort(&rows, &sort); + json_object_put(result, "rows", + ovsdb_row_set_to_json(&rows, &columns)); + + ovsdb_row_set_destroy(&rows); + } + + ovsdb_column_set_destroy(&columns); + ovsdb_column_set_destroy(&sort); + ovsdb_condition_destroy(&condition); + + return error; +} + +struct update_row_cbdata { + size_t n_matches; + struct ovsdb_txn *txn; + const struct ovsdb_row *row; + const struct ovsdb_column_set *columns; +}; + +static bool +update_row_cb(const struct ovsdb_row *row, void *ur_) +{ + struct update_row_cbdata *ur = ur_; + + ur->n_matches++; + if (!ovsdb_row_equal_columns(row, ur->row, ur->columns)) { + ovsdb_row_update_columns(ovsdb_txn_row_modify(ur->txn, row), + ur->row, ur->columns); + } + + return true; +} + +struct ovsdb_error * +ovsdb_execute_update(struct ovsdb_execution *x, struct ovsdb_parser *parser, + struct json *result) +{ + struct ovsdb_table *table; + const struct json *where; + struct ovsdb_condition condition = OVSDB_CONDITION_INITIALIZER; + struct ovsdb_column_set columns = OVSDB_COLUMN_SET_INITIALIZER; + struct ovsdb_row *row = NULL; + struct update_row_cbdata ur; + struct ovsdb_error *error; + + table = parse_table(x, parser, "table"); + where = ovsdb_parser_member(parser, "where", OP_ARRAY); + error = ovsdb_parser_get_error(parser); + if (!error) { + error = parse_row(parser, "row", table, x->symtab, &row, &columns); + } + if (!error) { + error = ovsdb_condition_from_json(table->schema, where, x->symtab, + &condition); + } + if (!error) { + ur.n_matches = 0; + ur.txn = x->txn; + ur.row = row; + ur.columns = &columns; + ovsdb_query(table, &condition, update_row_cb, &ur); + json_object_put(result, "count", json_integer_create(ur.n_matches)); + } + + ovsdb_row_destroy(row); + ovsdb_column_set_destroy(&columns); + ovsdb_condition_destroy(&condition); + + return error; +} + +struct delete_row_cbdata { + size_t n_matches; + const struct ovsdb_table *table; + struct ovsdb_txn *txn; +}; + +static bool +delete_row_cb(const struct ovsdb_row *row, void *dr_) +{ + struct delete_row_cbdata *dr = dr_; + + dr->n_matches++; + ovsdb_txn_row_delete(dr->txn, row); + + return true; +} + +struct ovsdb_error * +ovsdb_execute_delete(struct ovsdb_execution *x, struct ovsdb_parser *parser, + struct json *result) +{ + struct ovsdb_table *table; + const struct json *where; + struct ovsdb_condition condition = OVSDB_CONDITION_INITIALIZER; + struct ovsdb_error *error; + + where = ovsdb_parser_member(parser, "where", OP_ARRAY); + table = parse_table(x, parser, "table"); + error = ovsdb_parser_get_error(parser); + if (!error) { + error = ovsdb_condition_from_json(table->schema, where, x->symtab, + &condition); + } + if (!error) { + struct delete_row_cbdata dr; + + dr.n_matches = 0; + dr.table = table; + dr.txn = x->txn; + ovsdb_query(table, &condition, delete_row_cb, &dr); + + json_object_put(result, "count", json_integer_create(dr.n_matches)); + } + + ovsdb_condition_destroy(&condition); + + return error; +} + +struct wait_auxdata { + struct ovsdb_row_hash *actual; + struct ovsdb_row_hash *expected; + bool *equal; +}; + +static bool +ovsdb_execute_wait_query_cb(const struct ovsdb_row *row, void *aux_) +{ + struct wait_auxdata *aux = aux_; + + if (ovsdb_row_hash_contains(aux->expected, row)) { + ovsdb_row_hash_insert(aux->actual, row); + return true; + } else { + /* The query row isn't in the expected result set, so the actual and + * expected results sets definitely differ and we can short-circuit the + * rest of the query. */ + *aux->equal = false; + return false; + } +} + +static struct ovsdb_error * +ovsdb_execute_wait(struct ovsdb_execution *x, struct ovsdb_parser *parser, + struct json *result UNUSED) +{ + struct ovsdb_table *table; + const struct json *timeout, *where, *columns_json, *until, *rows; + struct ovsdb_condition condition = OVSDB_CONDITION_INITIALIZER; + struct ovsdb_column_set columns = OVSDB_COLUMN_SET_INITIALIZER; + struct ovsdb_row_hash expected = OVSDB_ROW_HASH_INITIALIZER(expected); + struct ovsdb_row_hash actual = OVSDB_ROW_HASH_INITIALIZER(actual); + struct ovsdb_error *error; + struct wait_auxdata aux; + long long int timeout_msec = 0; + size_t i; + + timeout = ovsdb_parser_member(parser, "timeout", OP_NUMBER | OP_OPTIONAL); + where = ovsdb_parser_member(parser, "where", OP_ARRAY); + columns_json = ovsdb_parser_member(parser, "columns", + OP_ARRAY | OP_OPTIONAL); + until = ovsdb_parser_member(parser, "until", OP_STRING); + rows = ovsdb_parser_member(parser, "rows", OP_ARRAY); + table = parse_table(x, parser, "table"); + error = ovsdb_parser_get_error(parser); + if (!error) { + error = ovsdb_condition_from_json(table->schema, where, x->symtab, + &condition); + } + if (!error) { + error = ovsdb_column_set_from_json(columns_json, table, &columns); + } + if (!error) { + if (timeout) { + timeout_msec = MIN(LLONG_MAX, json_real(timeout)); + if (timeout_msec < 0) { + error = ovsdb_syntax_error(timeout, NULL, + "timeout must be nonnegative"); + } else if (timeout_msec < x->timeout_msec) { + x->timeout_msec = timeout_msec; + } + } else { + timeout_msec = LLONG_MAX; + } + if (strcmp(json_string(until), "==") + && strcmp(json_string(until), "!=")) { + error = ovsdb_syntax_error(until, NULL, + "\"until\" must be \"==\" or \"!=\""); + } + } + if (!error) { + /* Parse "rows" into 'expected'. */ + ovsdb_row_hash_init(&expected, &columns); + for (i = 0; i < rows->u.array.n; i++) { + struct ovsdb_error *error; + struct ovsdb_row *row; + + row = ovsdb_row_create(table); + error = ovsdb_row_from_json(row, rows->u.array.elems[i], x->symtab, + NULL); + if (error) { + break; + } + + if (!ovsdb_row_hash_insert(&expected, row)) { + /* XXX Perhaps we should abort with an error or log a + * warning. */ + ovsdb_row_destroy(row); + } + } + } + if (!error) { + /* Execute query. */ + bool equal = true; + ovsdb_row_hash_init(&actual, &columns); + aux.actual = &actual; + aux.expected = &expected; + aux.equal = &equal; + ovsdb_query(table, &condition, ovsdb_execute_wait_query_cb, &aux); + if (equal) { + /* We know that every row in 'actual' is also in 'expected'. We + * also know that all of the rows in 'actual' are distinct and that + * all of the rows in 'expected' are distinct. Therefore, if + * 'actual' and 'expected' have the same number of rows, then they + * have the same content. */ + size_t n_actual = ovsdb_row_hash_count(&actual); + size_t n_expected = ovsdb_row_hash_count(&expected); + equal = n_actual == n_expected; + } + if (!strcmp(json_string(until), "==") != equal) { + if (timeout && x->elapsed_msec >= timeout_msec) { + if (x->elapsed_msec) { + error = ovsdb_error("timed out", + "\"wait\" timed out after %lld ms", + x->elapsed_msec); + } else { + error = ovsdb_error("timed out", "\"wait\" timed out"); + } + } else { + /* ovsdb_execute() will change this, if triggers really are + * supported. */ + error = ovsdb_error("not supported", "triggers not supported"); + } + } + } + + + ovsdb_row_hash_destroy(&expected, true); + ovsdb_row_hash_destroy(&actual, false); + ovsdb_column_set_destroy(&columns); + ovsdb_condition_destroy(&condition); + + return error; +} diff --git a/ovsdb/file.c b/ovsdb/file.c new file mode 100644 index 00000000..4883d76f --- /dev/null +++ b/ovsdb/file.c @@ -0,0 +1,360 @@ +/* Copyright (c) 2009 Nicira Networks + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "file.h" + +#include +#include +#include +#include +#include +#include + +#include "json.h" +#include "lockfile.h" +#include "ovsdb-error.h" +#include "sha1.h" +#include "util.h" + +#define THIS_MODULE VLM_ovsdb_file +#include "vlog.h" + +enum ovsdb_file_mode { + OVSDB_FILE_READ, + OVSDB_FILE_WRITE +}; + +struct ovsdb_file { + off_t offset; + char *name; + struct lockfile *lockfile; + FILE *stream; + struct ovsdb_error *read_error; + struct ovsdb_error *write_error; + enum ovsdb_file_mode mode; +}; + +struct ovsdb_error * +ovsdb_file_open(const char *name, int flags, struct ovsdb_file **filep) +{ + struct lockfile *lockfile; + struct ovsdb_error *error; + struct ovsdb_file *file; + struct stat s; + FILE *stream; + int accmode; + int fd; + + *filep = NULL; + + accmode = flags & O_ACCMODE; + if (accmode == O_RDWR || accmode == O_WRONLY) { + int retval = lockfile_lock(name, 0, &lockfile); + if (retval) { + error = ovsdb_io_error(retval, "%s: failed to lock lockfile", + name); + goto error; + } + } else { + lockfile = NULL; + } + + fd = open(name, flags, 0666); + if (fd < 0) { + const char *op = flags & O_CREAT && flags & O_EXCL ? "create" : "open"; + error = ovsdb_io_error(errno, "%s: %s failed", op, name); + goto error_unlock; + } + + if (!fstat(fd, &s) && s.st_size == 0) { + /* It's (probably) a new file so fsync() its parent directory to ensure + * that its directory entry is committed to disk. */ + char *dir = dir_name(name); + int dirfd = open(dir, O_RDONLY); + if (dirfd >= 0) { + if (fsync(dirfd) && errno != EINVAL) { + VLOG_ERR("%s: fsync failed (%s)", dir, strerror(errno)); + } + close(dirfd); + } else { + VLOG_ERR("%s: open failed (%s)", dir, strerror(errno)); + } + free(dir); + } + + stream = fdopen(fd, (accmode == O_RDONLY ? "rb" + : accmode == O_WRONLY ? "wb" + : "w+b")); + if (!stream) { + error = ovsdb_io_error(errno, "%s: fdopen failed", name); + goto error_close; + } + + file = xmalloc(sizeof *file); + file->name = xstrdup(name); + file->lockfile = lockfile; + file->stream = stream; + file->offset = 0; + file->read_error = NULL; + file->write_error = NULL; + file->mode = OVSDB_FILE_READ; + *filep = file; + return NULL; + +error_close: + close(fd); +error_unlock: + lockfile_unlock(lockfile); +error: + return error; +} + +void +ovsdb_file_close(struct ovsdb_file *file) +{ + if (file) { + free(file->name); + fclose(file->stream); + lockfile_unlock(file->lockfile); + ovsdb_error_destroy(file->read_error); + ovsdb_error_destroy(file->write_error); + free(file); + } +} + +static const char magic[] = "OVSDB JSON "; + +static bool +parse_header(char *header, unsigned long int *length, + uint8_t sha1[SHA1_DIGEST_SIZE]) +{ + char *p; + + /* 'header' must consist of a magic string... */ + if (strncmp(header, magic, strlen(magic))) { + return false; + } + + /* ...followed by a length in bytes... */ + *length = strtoul(header + strlen(magic), &p, 10); + if (!*length || *length == ULONG_MAX || *p != ' ') { + return false; + } + p++; + + /* ...followed by a SHA-1 hash... */ + if (!sha1_from_hex(sha1, p)) { + return false; + } + p += SHA1_HEX_DIGEST_LEN; + + /* ...and ended by a new-line. */ + if (*p != '\n') { + return false; + } + + return true; +} + +struct ovsdb_file_read_cbdata { + char input[4096]; + struct ovsdb_file *file; + int error; + unsigned long length; +}; + +static struct ovsdb_error * +parse_body(struct ovsdb_file *file, off_t offset, unsigned long int length, + uint8_t sha1[SHA1_DIGEST_SIZE], struct json **jsonp) +{ + unsigned long int bytes_left; + struct json_parser *parser; + struct sha1_ctx ctx; + + sha1_init(&ctx); + parser = json_parser_create(JSPF_TRAILER); + + bytes_left = length; + while (length > 0) { + char input[BUFSIZ]; + int chunk; + + chunk = MIN(length, sizeof input); + if (fread(input, 1, chunk, file->stream) != chunk) { + json_parser_abort(parser); + return ovsdb_io_error(ferror(file->stream) ? errno : EOF, + "%s: error reading %lu bytes " + "starting at offset %lld", file->name, + length, (long long int) offset); + } + sha1_update(&ctx, input, chunk); + json_parser_feed(parser, input, chunk); + length -= chunk; + } + + sha1_final(&ctx, sha1); + *jsonp = json_parser_finish(parser); + return NULL; +} + +struct ovsdb_error * +ovsdb_file_read(struct ovsdb_file *file, struct json **jsonp) +{ + uint8_t expected_sha1[SHA1_DIGEST_SIZE]; + uint8_t actual_sha1[SHA1_DIGEST_SIZE]; + struct ovsdb_error *error; + off_t data_offset; + unsigned long data_length; + struct json *json; + char header[128]; + + *jsonp = json = NULL; + + if (file->read_error) { + return ovsdb_error_clone(file->read_error); + } else if (file->mode == OVSDB_FILE_WRITE) { + return OVSDB_BUG("reading file in write mode"); + } + + if (!fgets(header, sizeof header, file->stream)) { + if (feof(file->stream)) { + error = NULL; + } else { + error = ovsdb_io_error(errno, "%s: read failed", file->name); + } + goto error; + } + + if (!parse_header(header, &data_length, expected_sha1)) { + error = ovsdb_syntax_error(NULL, NULL, "%s: parse error at offset " + "%lld in header line \"%.*s\"", + file->name, (long long int) file->offset, + (int) strcspn(header, "\n"), header); + goto error; + } + + data_offset = file->offset + strlen(header); + error = parse_body(file, data_offset, data_length, actual_sha1, &json); + if (error) { + goto error; + } + + if (memcmp(expected_sha1, actual_sha1, SHA1_DIGEST_SIZE)) { + error = ovsdb_syntax_error(NULL, NULL, "%s: %lu bytes starting at " + "offset %lld have SHA-1 hash "SHA1_FMT" " + "but should have hash "SHA1_FMT, + file->name, data_length, + (long long int) data_offset, + SHA1_ARGS(actual_sha1), + SHA1_ARGS(expected_sha1)); + goto error; + } + + if (json->type == JSON_STRING) { + error = ovsdb_syntax_error(NULL, NULL, "%s: %lu bytes starting at " + "offset %lld are not valid JSON (%s)", + file->name, data_length, + (long long int) data_offset, + json->u.string); + goto error; + } + + file->offset = data_offset + data_length; + *jsonp = json; + return 0; + +error: + file->read_error = ovsdb_error_clone(error); + json_destroy(json); + return error; +} + +struct ovsdb_error * +ovsdb_file_write(struct ovsdb_file *file, struct json *json) +{ + uint8_t sha1[SHA1_DIGEST_SIZE]; + struct ovsdb_error *error; + char *json_string; + char header[128]; + size_t length; + + json_string = NULL; + + if (file->write_error) { + return ovsdb_error_clone(file->write_error); + } else if (file->mode == OVSDB_FILE_READ) { + file->mode = OVSDB_FILE_WRITE; + if (fseeko(file->stream, file->offset, SEEK_SET)) { + error = ovsdb_io_error(errno, "%s: cannot seek to offset %lld", + file->name, (long long int) file->offset); + goto error; + } + if (ftruncate(fileno(file->stream), file->offset)) { + error = ovsdb_io_error(errno, "%s: cannot truncate to length %lld", + file->name, (long long int) file->offset); + goto error; + } + } + + if (json->type != JSON_OBJECT && json->type != JSON_ARRAY) { + error = OVSDB_BUG("bad JSON type"); + goto error; + } + + /* Compose content. Add a new-line (replacing the null terminator) to make + * the file easier to read, even though it has no semantic value. */ + json_string = json_to_string(json, 0); + length = strlen(json_string) + 1; + json_string[length - 1] = '\n'; + + /* Compose header. */ + sha1_bytes(json_string, length, sha1); + snprintf(header, sizeof header, "%s%zu "SHA1_FMT"\n", + magic, length, SHA1_ARGS(sha1)); + + /* Write. */ + if (fwrite(header, strlen(header), 1, file->stream) != 1 + || fwrite(json_string, length, 1, file->stream) != 1 + || fflush(file->stream)) + { + error = ovsdb_io_error(errno, "%s: write failed", file->name); + + /* Remove any partially written data, ignoring errors since there is + * nothing further we can do. */ + ftruncate(fileno(file->stream), file->offset); + + goto error; + } + + file->offset += strlen(header) + length; + free(json_string); + return 0; + +error: + file->write_error = ovsdb_error_clone(error); + free(json_string); + return error; +} + +struct ovsdb_error * +ovsdb_file_commit(struct ovsdb_file *file) +{ + if (fsync(fileno(file->stream))) { + return ovsdb_io_error(errno, "%s: fsync failed", file->name); + } + return 0; +} diff --git a/ovsdb/file.h b/ovsdb/file.h new file mode 100644 index 00000000..5178140a --- /dev/null +++ b/ovsdb/file.h @@ -0,0 +1,36 @@ +/* Copyright (c) 2009 Nicira Networks + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OVSDB_FILE_H +#define OVSDB_FILE_H 1 + +#include +#include "compiler.h" + +struct json; +struct ovsdb_file; + +struct ovsdb_error *ovsdb_file_open(const char *name, int flags, + struct ovsdb_file **) WARN_UNUSED_RESULT; +void ovsdb_file_close(struct ovsdb_file *); + +struct ovsdb_error *ovsdb_file_read(struct ovsdb_file *, struct json **) + WARN_UNUSED_RESULT; +struct ovsdb_error *ovsdb_file_write(struct ovsdb_file *, struct json *) + WARN_UNUSED_RESULT; +struct ovsdb_error *ovsdb_file_commit(struct ovsdb_file *) + WARN_UNUSED_RESULT; + +#endif /* ovsdb/file.h */ diff --git a/ovsdb/jsonrpc-server.c b/ovsdb/jsonrpc-server.c new file mode 100644 index 00000000..36c9a7a2 --- /dev/null +++ b/ovsdb/jsonrpc-server.c @@ -0,0 +1,362 @@ +/* Copyright (c) 2009 Nicira Networks + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "jsonrpc-server.h" + +#include + +#include "json.h" +#include "jsonrpc.h" +#include "ovsdb.h" +#include "stream.h" +#include "svec.h" +#include "timeval.h" +#include "trigger.h" + +#define THIS_MODULE VLM_ovsdb_jsonrpc_server +#include "vlog.h" + +struct ovsdb_jsonrpc_trigger { + struct ovsdb_trigger trigger; + struct ovsdb_jsonrpc_session *session; + struct hmap_node hmap_node; /* Element in session's trigger table. */ + struct json *id; +}; + +static struct ovsdb_jsonrpc_trigger *ovsdb_jsonrpc_trigger_find( + struct ovsdb_jsonrpc_session *, const struct json *id, size_t hash); +static void ovsdb_jsonrpc_trigger_complete(struct ovsdb_jsonrpc_trigger *); + +struct ovsdb_jsonrpc_session { + struct ovsdb_jsonrpc_server *server; + struct list node; /* Element in server's sessions list. */ + struct jsonrpc *rpc; + struct hmap triggers; + struct list completions; /* Completed triggers. */ +}; + +static void ovsdb_jsonrpc_session_open(struct ovsdb_jsonrpc_server *, + struct stream *); +static void ovsdb_jsonrpc_session_close(struct ovsdb_jsonrpc_session *); +static void ovsdb_jsonrpc_session_got_request(struct ovsdb_jsonrpc_session *, + struct jsonrpc_msg *); +static void ovsdb_jsonrpc_session_got_notify(struct ovsdb_jsonrpc_session *, + struct jsonrpc_msg *); + +struct ovsdb_jsonrpc_server { + struct ovsdb *db; + + struct list sessions; /* List of "struct ovsdb_jsonrpc_session"s. */ + unsigned int n_sessions, max_sessions; + unsigned int max_triggers; + + struct pstream **listeners; + size_t n_listeners, allocated_listeners; +}; + +static void ovsdb_jsonrpc_server_listen(struct ovsdb_jsonrpc_server *, + struct pstream *); + +int +ovsdb_jsonrpc_server_create(struct ovsdb *db, const struct svec *active, + const struct svec *passive, + struct ovsdb_jsonrpc_server **serverp) +{ + struct ovsdb_jsonrpc_server *server; + const char *name; + int retval = 0; + size_t i; + + server = xzalloc(sizeof *server); + server->db = db; + server->max_sessions = 64; + server->max_triggers = 64; + list_init(&server->sessions); + + SVEC_FOR_EACH (i, name, active) { + struct stream *stream; + int error; + + error = stream_open(name, &stream); + if (!error) { + ovsdb_jsonrpc_session_open(server, stream); + } else { + ovs_error(error, "%s: connection failed", name); + retval = error; + } + } + + SVEC_FOR_EACH (i, name, passive) { + struct pstream *pstream; + int error; + + error = pstream_open(name, &pstream); + if (!error) { + ovsdb_jsonrpc_server_listen(server, pstream); + } else { + ovs_error(error, "failed to listen on %s", name); + retval = error; + } + } + + *serverp = server; + return retval; +} + +void +ovsdb_jsonrpc_server_run(struct ovsdb_jsonrpc_server *svr) +{ + struct ovsdb_jsonrpc_session *s, *next; + size_t i; + + /* Accept new connections. */ + for (i = 0; i < svr->n_listeners && svr->n_sessions < svr->max_sessions;) { + struct pstream *listener = svr->listeners[i]; + struct stream *stream; + int error; + + error = pstream_accept(listener, &stream); + if (!error) { + ovsdb_jsonrpc_session_open(svr, stream); + } else if (error == EAGAIN) { + i++; + } else if (error) { + VLOG_WARN("%s: accept failed: %s", + pstream_get_name(listener), strerror(error)); + pstream_close(listener); + svr->listeners[i] = svr->listeners[--svr->n_listeners]; + } + } + + /* Handle each session. */ + LIST_FOR_EACH_SAFE (s, next, struct ovsdb_jsonrpc_session, node, + &svr->sessions) { + struct jsonrpc_msg *msg; + int error; + + jsonrpc_run(s->rpc); + + while (!list_is_empty(&s->completions)) { + struct ovsdb_jsonrpc_trigger *t + = CONTAINER_OF(s->completions.next, + struct ovsdb_jsonrpc_trigger, trigger.node); + ovsdb_jsonrpc_trigger_complete(t); + } + + if (!jsonrpc_get_backlog(s->rpc) && !jsonrpc_recv(s->rpc, &msg)) { + if (msg->type == JSONRPC_REQUEST) { + ovsdb_jsonrpc_session_got_request(s, msg); + } else if (msg->type == JSONRPC_NOTIFY) { + ovsdb_jsonrpc_session_got_notify(s, msg); + } else { + VLOG_WARN("%s: received unexpected %s message", + jsonrpc_get_name(s->rpc), + jsonrpc_msg_type_to_string(msg->type)); + jsonrpc_error(s->rpc, EPROTO); + jsonrpc_msg_destroy(msg); + } + } + + error = jsonrpc_get_status(s->rpc); + if (error) { + ovsdb_jsonrpc_session_close(s); + } + } +} + +void +ovsdb_jsonrpc_server_wait(struct ovsdb_jsonrpc_server *svr) +{ + struct ovsdb_jsonrpc_session *s; + + if (svr->n_sessions < svr->max_sessions) { + size_t i; + + for (i = 0; i < svr->n_sessions; i++) { + pstream_wait(svr->listeners[i]); + } + } + + LIST_FOR_EACH (s, struct ovsdb_jsonrpc_session, node, &svr->sessions) { + jsonrpc_wait(s->rpc); + if (!jsonrpc_get_backlog(s->rpc)) { + jsonrpc_recv_wait(s->rpc); + } + } +} + +static void +ovsdb_jsonrpc_server_listen(struct ovsdb_jsonrpc_server *svr, + struct pstream *pstream) +{ + if (svr->n_listeners >= svr->allocated_listeners) { + svr->listeners = x2nrealloc(svr->listeners, &svr->allocated_listeners, + sizeof *svr->listeners); + } + svr->listeners[svr->n_listeners++] = pstream; +} + +static struct ovsdb_jsonrpc_trigger * +ovsdb_jsonrpc_trigger_find(struct ovsdb_jsonrpc_session *s, + const struct json *id, size_t hash) +{ + struct ovsdb_jsonrpc_trigger *t; + + HMAP_FOR_EACH_WITH_HASH (t, struct ovsdb_jsonrpc_trigger, hmap_node, hash, + &s->triggers) { + if (json_equal(t->id, id)) { + return t; + } + } + + return NULL; +} + +static void +ovsdb_jsonrpc_trigger_complete(struct ovsdb_jsonrpc_trigger *t) +{ + struct ovsdb_jsonrpc_session *s = t->session; + + if (!jsonrpc_get_status(s->rpc)) { + struct jsonrpc_msg *reply; + struct json *result; + + result = ovsdb_trigger_steal_result(&t->trigger); + if (result) { + reply = jsonrpc_create_reply(result, t->id); + } else { + reply = jsonrpc_create_error(json_string_create("canceled"), + t->id); + } + jsonrpc_send(s->rpc, reply); + } + + json_destroy(t->id); + ovsdb_trigger_destroy(&t->trigger); + hmap_remove(&s->triggers, &t->hmap_node); + free(t); +} + +static void +ovsdb_jsonrpc_session_open(struct ovsdb_jsonrpc_server *svr, + struct stream *stream) +{ + struct ovsdb_jsonrpc_session *s; + + s = xzalloc(sizeof *s); + s->server = svr; + list_push_back(&svr->sessions, &s->node); + s->rpc = jsonrpc_open(stream); + hmap_init(&s->triggers); + list_init(&s->completions); +} + +static void +ovsdb_jsonrpc_session_close(struct ovsdb_jsonrpc_session *s) +{ + struct ovsdb_jsonrpc_trigger *t, *next; + + jsonrpc_error(s->rpc, EOF); + HMAP_FOR_EACH_SAFE (t, next, struct ovsdb_jsonrpc_trigger, hmap_node, + &s->triggers) { + ovsdb_jsonrpc_trigger_complete(t); + } + + jsonrpc_close(s->rpc); + + list_remove(&s->node); + s->server->n_sessions--; +} + +static struct jsonrpc_msg * +execute_transaction(struct ovsdb_jsonrpc_session *s, + struct jsonrpc_msg *request) +{ + struct ovsdb_jsonrpc_trigger *t; + size_t hash; + + /* Check for duplicate ID. */ + hash = json_hash(request->id, 0); + t = ovsdb_jsonrpc_trigger_find(s, request->id, hash); + if (t) { + return jsonrpc_create_error( + json_string_create("duplicate request ID"), request->id); + } + + /* Insert into trigger table. */ + t = xmalloc(sizeof *t); + ovsdb_trigger_init(s->server->db, + &t->trigger, request->params, &s->completions, + time_msec()); + t->session = s; + t->id = request->id; + hmap_insert(&s->triggers, &t->hmap_node, hash); + + request->id = NULL; + request->params = NULL; + + /* Complete early if possible. */ + if (ovsdb_trigger_is_complete(&t->trigger)) { + ovsdb_jsonrpc_trigger_complete(t); + } + + return NULL; +} + +static void +ovsdb_jsonrpc_session_got_request(struct ovsdb_jsonrpc_session *s, + struct jsonrpc_msg *request) +{ + struct jsonrpc_msg *reply; + + if (!strcmp(request->method, "transact")) { + reply = execute_transaction(s, request); + } else if (!strcmp(request->method, "get_schema")) { + reply = jsonrpc_create_reply( + ovsdb_schema_to_json(s->server->db->schema), request->id); + } else { + reply = jsonrpc_create_error(json_string_create("unknown method"), + request->id); + } + + if (reply) { + jsonrpc_msg_destroy(request); + jsonrpc_send(s->rpc, reply); + } +} + +static void +execute_cancel(struct ovsdb_jsonrpc_session *s, struct jsonrpc_msg *request) +{ + size_t hash = json_hash(request->id, 0); + struct ovsdb_jsonrpc_trigger *t; + + t = ovsdb_jsonrpc_trigger_find(s, request->params, hash); + if (t) { + ovsdb_jsonrpc_trigger_complete(t); + } +} + +static void +ovsdb_jsonrpc_session_got_notify(struct ovsdb_jsonrpc_session *s, + struct jsonrpc_msg *request) +{ + if (!strcmp(request->method, "cancel")) { + execute_cancel(s, request); + } + jsonrpc_msg_destroy(request); +} diff --git a/ovsdb/jsonrpc-server.h b/ovsdb/jsonrpc-server.h new file mode 100644 index 00000000..49b5f8a9 --- /dev/null +++ b/ovsdb/jsonrpc-server.h @@ -0,0 +1,29 @@ +/* Copyright (c) 2009 Nicira Networks + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OVSDB_JSONRPC_SERVER_H +#define OVSDB_JSONRPC_SERVER_H 1 + +struct ovsdb; +struct ovsdb_jsonrpc_server; +struct svec; + +int ovsdb_jsonrpc_server_create(struct ovsdb *, const struct svec *active, + const struct svec *passive, + struct ovsdb_jsonrpc_server **); +void ovsdb_jsonrpc_server_run(struct ovsdb_jsonrpc_server *); +void ovsdb_jsonrpc_server_wait(struct ovsdb_jsonrpc_server *); + +#endif /* ovsdb/jsonrpc-server.h */ diff --git a/ovsdb/ovsdb-server.c b/ovsdb/ovsdb-server.c new file mode 100644 index 00000000..17a9970e --- /dev/null +++ b/ovsdb/ovsdb-server.c @@ -0,0 +1,223 @@ +/* Copyright (c) 2009 Nicira Networks + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "ovsdb.h" + +#include +#include +#include + +#include "command-line.h" +#include "daemon.h" +#include "fault.h" +#include "json.h" +#include "jsonrpc.h" +#include "jsonrpc-server.h" +#include "leak-checker.h" +#include "list.h" +#include "ovsdb-error.h" +#include "poll-loop.h" +#include "process.h" +#include "stream.h" +#include "svec.h" +#include "timeval.h" +#include "trigger.h" +#include "util.h" +#include "unixctl.h" + +#include "vlog.h" +#define THIS_MODULE VLM_ovsdb_server + +static const struct jsonrpc_server_cbs ovsdb_jsonrpc_cbs; + +static void parse_options(int argc, char *argv[], char **file_namep, + struct svec *active, struct svec *passive); +static void usage(void) NO_RETURN; + +static void ovsdb_transact(struct unixctl_conn *, const char *args, void *db); + +int +main(int argc, char *argv[]) +{ + struct unixctl_server *unixctl; + struct ovsdb_jsonrpc_server *jsonrpc; + struct svec active, passive; + struct ovsdb_error *error; + struct ovsdb *db; + char *file_name; + int retval; + + set_program_name(argv[0]); + register_fault_handlers(); + time_init(); + vlog_init(); + signal(SIGPIPE, SIG_IGN); + process_init(); + + parse_options(argc, argv, &file_name, &active, &passive); + + error = ovsdb_open(file_name, false, &db); + if (error) { + ovs_fatal(0, "%s", ovsdb_error_to_string(error)); + } + + retval = ovsdb_jsonrpc_server_create(db, &active, &passive, &jsonrpc); + if (retval) { + ovs_fatal(retval, "failed to initialize JSON-RPC server for OVSDB"); + } + svec_destroy(&active); + svec_destroy(&passive); + + die_if_already_running(); + daemonize(); + + retval = unixctl_server_create(NULL, &unixctl); + if (retval) { + ovs_fatal(retval, "could not listen for control connections"); + } + + unixctl_command_register("ovsdb/transact", ovsdb_transact, db); + + for (;;) { + ovsdb_jsonrpc_server_run(jsonrpc); + unixctl_server_run(unixctl); + ovsdb_trigger_run(db, time_msec()); + + ovsdb_jsonrpc_server_wait(jsonrpc); + unixctl_server_wait(unixctl); + ovsdb_trigger_wait(db, time_msec()); + poll_block(); + } + + return 0; +} + +static void +parse_options(int argc, char *argv[], char **file_namep, + struct svec *active, struct svec *passive) +{ + enum { + OPT_DUMMY = UCHAR_MAX + 1, + OPT_CONNECT, + OPT_LISTEN, + VLOG_OPTION_ENUMS, + LEAK_CHECKER_OPTION_ENUMS + }; + static struct option long_options[] = { + {"connect", required_argument, 0, OPT_CONNECT}, + {"listen", required_argument, 0, OPT_LISTEN}, + {"help", no_argument, 0, 'h'}, + {"version", no_argument, 0, 'V'}, + DAEMON_LONG_OPTIONS, + VLOG_LONG_OPTIONS, + LEAK_CHECKER_LONG_OPTIONS, + {0, 0, 0, 0}, + }; + char *short_options = long_options_to_short_options(long_options); + + svec_init(active); + svec_init(passive); + for (;;) { + int c; + + c = getopt_long(argc, argv, short_options, long_options, NULL); + if (c == -1) { + break; + } + + switch (c) { + case OPT_CONNECT: + svec_add(active, optarg); + break; + + case OPT_LISTEN: + svec_add(passive, optarg); + break; + + case 'h': + usage(); + + case 'V': + OVS_PRINT_VERSION(0, 0); + exit(EXIT_SUCCESS); + + VLOG_OPTION_HANDLERS + DAEMON_OPTION_HANDLERS + LEAK_CHECKER_OPTION_HANDLERS + + case '?': + exit(EXIT_FAILURE); + + default: + abort(); + } + } + free(short_options); + + argc -= optind; + argv += optind; + + if (argc != 1) { + ovs_fatal(0, "database file is only non-option argument; " + "use --help for usage"); + } + + *file_namep = argv[0]; +} + +static void +usage(void) +{ + printf("%s: Open vSwitch database server\n" + "usage: %s [OPTIONS] DATABASE\n" + "where DATABASE is a database file in ovsdb format.\n", + program_name, program_name); + printf("\nJSON-RPC options (may be specified any number of times):\n" + " --connect=REMOTE make active connection to REMOTE\n" + " --listen=LOCAL passively listen on LOCAL\n"); + stream_usage("JSON-RPC", true, true); + daemon_usage(); + vlog_usage(); + printf("\nOther options:\n" + " -h, --help display this help message\n" + " -V, --version display version information\n"); + leak_checker_usage(); + exit(EXIT_SUCCESS); +} + +static void +ovsdb_transact(struct unixctl_conn *conn, const char *args, void *db_) +{ + struct ovsdb *db = db_; + struct json *request, *reply; + char *reply_string; + + /* Parse JSON. */ + request = json_from_string(args); + if (request->type == JSON_STRING) { + unixctl_command_reply(conn, 501, request->u.string); + json_destroy(request); + return; + } + + /* Execute command. */ + reply = ovsdb_execute(db, request, 0, NULL); + reply_string = json_to_string(reply, 0); + unixctl_command_reply(conn, 200, reply_string); + free(reply_string); + json_destroy(reply); +} diff --git a/ovsdb/ovsdb-tool.c b/ovsdb/ovsdb-tool.c new file mode 100644 index 00000000..5169653d --- /dev/null +++ b/ovsdb/ovsdb-tool.c @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2009 Nicira Networks. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "command-line.h" +#include "compiler.h" +#include "file.h" +#include "json.h" +#include "ovsdb.h" +#include "ovsdb-error.h" +#include "table.h" +#include "timeval.h" +#include "util.h" + +#include "vlog.h" +#define THIS_MODULE VLM_ovsdb_tool + +static const struct command all_commands[]; + +static void usage(void) NO_RETURN; +static void parse_options(int argc, char *argv[]); + +int +main(int argc, char *argv[]) +{ + set_program_name(argv[0]); + time_init(); + vlog_init(); + parse_options(argc, argv); + signal(SIGPIPE, SIG_IGN); + run_command(argc - optind, argv + optind, all_commands); + return 0; +} + +static void +parse_options(int argc, char *argv[]) +{ + static struct option long_options[] = { + {"verbose", optional_argument, 0, 'v'}, + {"help", no_argument, 0, 'h'}, + {"version", no_argument, 0, 'V'}, + {0, 0, 0, 0}, + }; + char *short_options = long_options_to_short_options(long_options); + + for (;;) { + int c; + + c = getopt_long(argc, argv, short_options, long_options, NULL); + if (c == -1) { + break; + } + + switch (c) { + case 'h': + usage(); + + case 'V': + OVS_PRINT_VERSION(0, 0); + exit(EXIT_SUCCESS); + + case 'v': + vlog_set_verbosity(optarg); + break; + + case '?': + exit(EXIT_FAILURE); + + default: + abort(); + } + } + free(short_options); +} + +static void +usage(void) +{ + printf("%s: Open vSwitch database management utility\n" + "usage: %s [OPTIONS] COMMAND [ARG...]\n" + " create DB SCHEMA create DB with the given SCHEMA\n" + " compact DB [DST] compact DB in-place (or to DST)\n" + " extract-schema DB print DB's schema on stdout\n" + " query DB TRNS execute read-only transaction on DB\n" + " transact DB TRNS execute read/write transaction on DB\n", + program_name, program_name); + vlog_usage(); + printf("\nOther options:\n" + " -h, --help display this help message\n" + " -V, --version display version information\n"); + exit(EXIT_SUCCESS); +} + +static struct json * +parse_json(const char *s) +{ + struct json *json = json_from_string(s); + if (json->type == JSON_STRING) { + ovs_fatal(0, "\"%s\": %s", s, json->u.string); + } + return json; +} + +static void +print_and_free_json(struct json *json) +{ + char *string = json_to_string(json, JSSF_SORT); + json_destroy(json); + puts(string); + free(string); +} + +static void +check_ovsdb_error(struct ovsdb_error *error) +{ + if (error) { + ovs_fatal(0, "%s", ovsdb_error_to_string(error)); + } +} + +static void +do_create(int argc UNUSED, char *argv[]) +{ + const char *db_file_name = argv[1]; + const char *schema_file_name = argv[2]; + struct ovsdb_schema *schema; + struct ovsdb_file *db_file; + struct json *json; + + /* Read schema from file and convert to JSON. */ + check_ovsdb_error(ovsdb_schema_from_file(schema_file_name, &schema)); + json = ovsdb_schema_to_json(schema); + + /* Create database file. */ + check_ovsdb_error(ovsdb_file_open(db_file_name, O_RDWR | O_CREAT | O_EXCL, + &db_file)); + check_ovsdb_error(ovsdb_file_write(db_file, json)); + check_ovsdb_error(ovsdb_file_commit(db_file)); + ovsdb_file_close(db_file); + + json_destroy(json); +} + +static void +transact(int flags, const char *db_file_name, const char *transaction) +{ + struct json *request, *result; + struct ovsdb *db; + + check_ovsdb_error(ovsdb_open(db_file_name, flags, &db)); + + request = parse_json(transaction); + result = ovsdb_execute(db, request, 0, NULL); + json_destroy(request); + + print_and_free_json(result); + ovsdb_destroy(db); +} + +static void +do_query(int argc UNUSED, char *argv[]) +{ + transact(O_RDONLY, argv[1], argv[2]); +} + +static void +do_transact(int argc UNUSED, char *argv[]) +{ + transact(O_RDWR, argv[1], argv[2]); +} + +static void +do_help(int argc UNUSED, char *argv[] UNUSED) +{ + usage(); +} + +static const struct command all_commands[] = { + { "create", 2, 2, do_create }, + { "query", 2, 2, do_query }, + { "transact", 2, 2, do_transact }, + { "help", 0, INT_MAX, do_help }, + { NULL, 0, 0, NULL }, +}; diff --git a/ovsdb/ovsdb.c b/ovsdb/ovsdb.c new file mode 100644 index 00000000..e653758f --- /dev/null +++ b/ovsdb/ovsdb.c @@ -0,0 +1,262 @@ +/* Copyright (c) 2009 Nicira Networks + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "ovsdb.h" + +#include + +#include "file.h" +#include "json.h" +#include "ovsdb-error.h" +#include "ovsdb-parser.h" +#include "table.h" +#include "transaction.h" + +#define THIS_MODULE VLM_ovsdb +#include "vlog.h" + +struct ovsdb_schema * +ovsdb_schema_create(const char *name, const char *comment) +{ + struct ovsdb_schema *schema; + + schema = xzalloc(sizeof *schema); + schema->name = xstrdup(name); + schema->comment = comment ? xstrdup(comment) : NULL; + shash_init(&schema->tables); + + return schema; +} + +void +ovsdb_schema_destroy(struct ovsdb_schema *schema) +{ + struct shash_node *node; + + SHASH_FOR_EACH (node, &schema->tables) { + ovsdb_table_schema_destroy(node->data); + } + shash_destroy(&schema->tables); + free(schema->comment); + free(schema->name); + free(schema); +} + +struct ovsdb_error * +ovsdb_schema_from_file(const char *file_name, struct ovsdb_schema **schemap) +{ + struct ovsdb_schema *schema; + struct ovsdb_error *error; + struct json *json; + + *schemap = NULL; + json = json_from_file(file_name); + if (json->type == JSON_STRING) { + error = ovsdb_error("failed to read schema", + "\"%s\" could not be read as JSON (%s)", + file_name, json_string(json)); + json_destroy(json); + return error; + } + + error = ovsdb_schema_from_json(json, &schema); + if (error) { + json_destroy(json); + return ovsdb_wrap_error(error, + "failed to parse \"%s\" as ovsdb schema", + file_name); + } + + *schemap = schema; + return NULL; +} + +struct ovsdb_error * +ovsdb_schema_from_json(struct json *json, struct ovsdb_schema **schemap) +{ + struct ovsdb_schema *schema; + const struct json *name, *comment, *tables; + struct ovsdb_error *error; + struct shash_node *node; + struct ovsdb_parser parser; + + *schemap = NULL; + + ovsdb_parser_init(&parser, json, "database schema"); + name = ovsdb_parser_member(&parser, "name", OP_ID); + comment = ovsdb_parser_member(&parser, "comment", OP_STRING | OP_OPTIONAL); + tables = ovsdb_parser_member(&parser, "tables", OP_OBJECT); + error = ovsdb_parser_finish(&parser); + if (error) { + return error; + } + + schema = ovsdb_schema_create(json_string(name), + comment ? json_string(comment) : NULL); + SHASH_FOR_EACH (node, json_object(tables)) { + struct ovsdb_table_schema *table; + + if (node->name[0] == '_') { + error = ovsdb_syntax_error(json, NULL, "names beginning with " + "\"_\" are reserved"); + } else { + error = ovsdb_table_schema_from_json(node->data, node->name, + &table); + } + if (error) { + ovsdb_schema_destroy(schema); + return error; + } + + shash_add(&schema->tables, table->name, table); + } + *schemap = schema; + return 0; +} + +struct json * +ovsdb_schema_to_json(const struct ovsdb_schema *schema) +{ + struct json *json, *tables; + struct shash_node *node; + + json = json_object_create(); + json_object_put_string(json, "name", schema->name); + if (schema->comment) { + json_object_put_string(json, "comment", schema->comment); + } + + tables = json_object_create(); + + SHASH_FOR_EACH (node, &schema->tables) { + struct ovsdb_table_schema *table = node->data; + json_object_put(tables, table->name, + ovsdb_table_schema_to_json(table)); + } + json_object_put(json, "tables", tables); + + return json; +} + +struct ovsdb * +ovsdb_create(struct ovsdb_file *file, struct ovsdb_schema *schema) +{ + struct shash_node *node; + struct ovsdb *db; + + db = xmalloc(sizeof *db); + db->schema = schema; + db->file = file; + list_init(&db->triggers); + db->run_triggers = false; + + shash_init(&db->tables); + SHASH_FOR_EACH (node, &schema->tables) { + struct ovsdb_table_schema *ts = node->data; + shash_add(&db->tables, node->name, ovsdb_table_create(ts)); + } + + return db; +} + +struct ovsdb_error * +ovsdb_open(const char *file_name, bool read_only, struct ovsdb **dbp) +{ + struct ovsdb_schema *schema; + struct ovsdb_error *error; + struct ovsdb_file *file; + struct json *json; + struct ovsdb *db; + + error = ovsdb_file_open(file_name, read_only ? O_RDONLY : O_RDWR, &file); + if (error) { + return error; + } + + error = ovsdb_file_read(file, &json); + if (error) { + return error; + } else if (!json) { + return ovsdb_io_error(EOF, "%s: database file contains no schema", + file_name); + } + + error = ovsdb_schema_from_json(json, &schema); + if (error) { + json_destroy(json); + return ovsdb_wrap_error(error, + "failed to parse \"%s\" as ovsdb schema", + file_name); + } + json_destroy(json); + + db = ovsdb_create(read_only ? file : NULL, schema); + while ((error = ovsdb_file_read(file, &json)) == NULL && json) { + struct ovsdb_txn *txn; + + error = ovsdb_txn_from_json(db, json, &txn); + json_destroy(json); + if (error) { + break; + } + + ovsdb_txn_commit(txn); + } + if (error) { + char *msg = ovsdb_error_to_string(error); + VLOG_WARN("%s", msg); + free(msg); + + ovsdb_error_destroy(error); + } + + if (read_only) { + ovsdb_file_close(file); + } + + *dbp = db; + return NULL; +} + +void +ovsdb_destroy(struct ovsdb *db) +{ + if (db) { + struct shash_node *node; + + /* Delete all the tables. This also deletes their schemas. */ + SHASH_FOR_EACH (node, &db->tables) { + struct ovsdb_table *table = node->data; + ovsdb_table_destroy(table); + } + shash_destroy(&db->tables); + + /* Clear the schema's hash of table schemas. The schemas, but not the + * table that points to them, were deleted in the previous step. */ + shash_destroy(&db->schema->tables); + + ovsdb_schema_destroy(db->schema); + ovsdb_file_close(db->file); + free(db); + } +} + +struct ovsdb_table * +ovsdb_get_table(const struct ovsdb *db, const char *name) +{ + return shash_find_data(&db->tables, name); +} diff --git a/ovsdb/ovsdb.h b/ovsdb/ovsdb.h new file mode 100644 index 00000000..3f62966a --- /dev/null +++ b/ovsdb/ovsdb.h @@ -0,0 +1,73 @@ +/* Copyright (c) 2009 Nicira Networks + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OVSDB_OVSDB_H +#define OVSDB_OVSDB_H 1 + +#include "compiler.h" +#include "hmap.h" +#include "list.h" +#include "shash.h" + +struct json; +struct uuid; + +/* Database schema. */ +struct ovsdb_schema { + char *name; + char *comment; + struct shash tables; /* Contains "struct ovsdb_table_schema *"s. */ +}; + +struct ovsdb_schema *ovsdb_schema_create(const char *name, + const char *comment); +void ovsdb_schema_destroy(struct ovsdb_schema *); + +struct ovsdb_error *ovsdb_schema_from_file(const char *file_name, + struct ovsdb_schema **) + WARN_UNUSED_RESULT; +struct ovsdb_error *ovsdb_schema_from_json(struct json *, + struct ovsdb_schema **) + WARN_UNUSED_RESULT; +struct json *ovsdb_schema_to_json(const struct ovsdb_schema *); + +/* Database. */ +struct ovsdb { + struct ovsdb_schema *schema; + struct ovsdb_file *file; /* Disk file (null for in-memory db). */ + struct shash tables; /* Contains "struct ovsdb_table *"s. */ + + /* Triggers. */ + struct list triggers; /* Contains "struct ovsdb_trigger"s. */ + bool run_triggers; +}; + +struct ovsdb *ovsdb_create(struct ovsdb_file *, struct ovsdb_schema *); +struct ovsdb_error *ovsdb_open(const char *file_name, bool read_only, + struct ovsdb **) + WARN_UNUSED_RESULT; +void ovsdb_destroy(struct ovsdb *); + +struct ovsdb_error *ovsdb_from_json(const struct json *, struct ovsdb **) + WARN_UNUSED_RESULT; +struct json *ovsdb_to_json(const struct ovsdb *); + +struct ovsdb_table *ovsdb_get_table(const struct ovsdb *, const char *); + +struct json *ovsdb_execute(struct ovsdb *, const struct json *params, + long long int elapsed_msec, + long long int *timeout_msec); + +#endif /* ovsdb/ovsdb.h */ diff --git a/ovsdb/query.c b/ovsdb/query.c new file mode 100644 index 00000000..878ac5b2 --- /dev/null +++ b/ovsdb/query.c @@ -0,0 +1,99 @@ +/* Copyright (c) 2009 Nicira Networks + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "query.h" + +#include "column.h" +#include "condition.h" +#include "row.h" +#include "table.h" + +void +ovsdb_query(struct ovsdb_table *table, const struct ovsdb_condition *cnd, + bool (*output_row)(const struct ovsdb_row *, void *aux), void *aux) +{ + if (cnd->n_clauses > 0 + && cnd->clauses[0].column->index == OVSDB_COL_UUID + && cnd->clauses[0].function == OVSDB_F_EQ) { + /* Optimize the case where the query has a clause of the form "uuid == + * ", since we have an index on UUID. */ + const struct ovsdb_row *row; + + row = ovsdb_table_get_row(table, &cnd->clauses[0].arg.keys[0].uuid); + if (row && row->table == table && ovsdb_condition_evaluate(row, cnd)) { + output_row(row, aux); + } + } else { + /* Linear scan. */ + const struct ovsdb_row *row, *next; + + HMAP_FOR_EACH_SAFE (row, next, struct ovsdb_row, hmap_node, + &table->rows) { + if (ovsdb_condition_evaluate(row, cnd) && !output_row(row, aux)) { + break; + } + } + } +} + +static bool +query_row_set_cb(const struct ovsdb_row *row, void *results_) +{ + struct ovsdb_row_set *results = results_; + ovsdb_row_set_add_row(results, row); + return true; +} + +void +ovsdb_query_row_set(struct ovsdb_table *table, + const struct ovsdb_condition *condition, + struct ovsdb_row_set *results) +{ + ovsdb_query(table, condition, query_row_set_cb, results); +} + +static bool +query_distinct_cb(const struct ovsdb_row *row, void *hash_) +{ + struct ovsdb_row_hash *hash = hash_; + ovsdb_row_hash_insert(hash, row); + return true; +} + +void +ovsdb_query_distinct(struct ovsdb_table *table, + const struct ovsdb_condition *condition, + const struct ovsdb_column_set *columns, + struct ovsdb_row_set *results) +{ + if (!columns || ovsdb_column_set_contains(columns, OVSDB_COL_UUID)) { + /* All the result rows are guaranteed to be distinct anyway. */ + return ovsdb_query_row_set(table, condition, results); + } else { + /* Use hash table to drop duplicates. */ + struct ovsdb_row_hash_node *node; + struct ovsdb_row_hash hash; + + ovsdb_row_hash_init(&hash, columns); + ovsdb_query(table, condition, query_distinct_cb, &hash); + HMAP_FOR_EACH (node, struct ovsdb_row_hash_node, hmap_node, + &hash.rows) { + ovsdb_row_set_add_row(results, node->row); + } + ovsdb_row_hash_destroy(&hash, false); + } +} diff --git a/ovsdb/query.h b/ovsdb/query.h new file mode 100644 index 00000000..f5cfe2e6 --- /dev/null +++ b/ovsdb/query.h @@ -0,0 +1,37 @@ +/* Copyright (c) 2009 Nicira Networks + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OVSDB_QUERY_H +#define OVSDB_QUERY_H 1 + +#include + +struct ovsdb_column_set; +struct ovsdb_condition; +struct ovsdb_row; +struct ovsdb_row_set; +struct ovsdb_table; +struct ovsdb_txn; + +void ovsdb_query(struct ovsdb_table *, const struct ovsdb_condition *, + bool (*output_row)(const struct ovsdb_row *, void *aux), + void *aux); +void ovsdb_query_row_set(struct ovsdb_table *, const struct ovsdb_condition *, + struct ovsdb_row_set *); +void ovsdb_query_distinct(struct ovsdb_table *, const struct ovsdb_condition *, + const struct ovsdb_column_set *, + struct ovsdb_row_set *); + +#endif /* ovsdb/query.h */ diff --git a/ovsdb/row.c b/ovsdb/row.c new file mode 100644 index 00000000..1b819420 --- /dev/null +++ b/ovsdb/row.c @@ -0,0 +1,386 @@ +/* Copyright (c) 2009 Nicira Networks + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "row.h" + +#include +#include + +#include "json.h" +#include "ovsdb-error.h" +#include "shash.h" +#include "sort.h" +#include "table.h" + +static struct ovsdb_row * +allocate_row(const struct ovsdb_table *table) +{ + size_t n_fields = shash_count(&table->schema->columns); + size_t row_size = (offsetof(struct ovsdb_row, fields) + + sizeof(struct ovsdb_datum) * n_fields); + struct ovsdb_row *row = xmalloc(row_size); + row->table = (struct ovsdb_table *) table; + row->txn_row = NULL; + return row; +} + +struct ovsdb_row * +ovsdb_row_create(const struct ovsdb_table *table) +{ + struct shash_node *node; + struct ovsdb_row *row; + + row = allocate_row(table); + SHASH_FOR_EACH (node, &table->schema->columns) { + const struct ovsdb_column *column = node->data; + ovsdb_datum_init_default(&row->fields[column->index], &column->type); + } + return row; +} + +struct ovsdb_row * +ovsdb_row_clone(const struct ovsdb_row *old) +{ + const struct ovsdb_table *table = old->table; + const struct shash_node *node; + struct ovsdb_row *new; + + new = allocate_row(table); + SHASH_FOR_EACH (node, &table->schema->columns) { + const struct ovsdb_column *column = node->data; + ovsdb_datum_clone(&new->fields[column->index], + &old->fields[column->index], + &column->type); + } + return new; +} + +/* The caller is responsible for ensuring that 'row' has been removed from its + * table and that it is not participating in a transaction. */ +void +ovsdb_row_destroy(struct ovsdb_row *row) +{ + if (row) { + const struct ovsdb_table *table = row->table; + const struct shash_node *node; + + SHASH_FOR_EACH (node, &table->schema->columns) { + const struct ovsdb_column *column = node->data; + ovsdb_datum_destroy(&row->fields[column->index], &column->type); + } + free(row); + } +} + +uint32_t +ovsdb_row_hash_columns(const struct ovsdb_row *row, + const struct ovsdb_column_set *columns, + uint32_t basis) +{ + size_t i; + + for (i = 0; i < columns->n_columns; i++) { + const struct ovsdb_column *column = columns->columns[i]; + basis = ovsdb_datum_hash(&row->fields[column->index], &column->type, + basis); + } + + return basis; +} + +int +ovsdb_row_compare_columns_3way(const struct ovsdb_row *a, + const struct ovsdb_row *b, + const struct ovsdb_column_set *columns) +{ + size_t i; + + for (i = 0; i < columns->n_columns; i++) { + const struct ovsdb_column *column = columns->columns[i]; + int cmp = ovsdb_datum_compare_3way(&a->fields[column->index], + &b->fields[column->index], + &column->type); + if (cmp) { + return cmp; + } + } + + return 0; +} + +bool +ovsdb_row_equal_columns(const struct ovsdb_row *a, + const struct ovsdb_row *b, + const struct ovsdb_column_set *columns) +{ + size_t i; + + for (i = 0; i < columns->n_columns; i++) { + const struct ovsdb_column *column = columns->columns[i]; + if (!ovsdb_datum_equals(&a->fields[column->index], + &b->fields[column->index], + &column->type)) { + return false; + } + } + + return true; +} + +void +ovsdb_row_update_columns(struct ovsdb_row *dst, + const struct ovsdb_row *src, + const struct ovsdb_column_set *columns) +{ + size_t i; + + for (i = 0; i < columns->n_columns; i++) { + const struct ovsdb_column *column = columns->columns[i]; + ovsdb_datum_destroy(&dst->fields[column->index], &column->type); + ovsdb_datum_clone(&dst->fields[column->index], + &src->fields[column->index], + &column->type); + } +} + +struct ovsdb_error * +ovsdb_row_from_json(struct ovsdb_row *row, const struct json *json, + const struct ovsdb_symbol_table *symtab, + struct ovsdb_column_set *included) +{ + struct ovsdb_table_schema *schema = row->table->schema; + struct ovsdb_error *error; + struct shash_node *node; + + if (json->type != JSON_OBJECT) { + return ovsdb_syntax_error(json, NULL, "row must be JSON object"); + } + + SHASH_FOR_EACH (node, json_object(json)) { + const char *column_name = node->name; + const struct ovsdb_column *column; + struct ovsdb_datum datum; + + column = ovsdb_table_schema_get_column(schema, column_name); + if (!column) { + return ovsdb_syntax_error(json, "unknown column", + "No column %s in table %s.", + column_name, schema->name); + } + + error = ovsdb_datum_from_json(&datum, &column->type, node->data, + symtab); + if (error) { + return error; + } + ovsdb_datum_swap(&row->fields[column->index], &datum); + ovsdb_datum_destroy(&datum, &column->type); + if (included) { + ovsdb_column_set_add(included, column); + } + } + + return NULL; +} + +static void +put_json_column(struct json *object, const struct ovsdb_row *row, + const struct ovsdb_column *column) +{ + json_object_put(object, column->name, + ovsdb_datum_to_json(&row->fields[column->index], + &column->type)); +} + +struct json * +ovsdb_row_to_json(const struct ovsdb_row *row, + const struct ovsdb_column_set *columns) +{ + struct json *json; + size_t i; + + json = json_object_create(); + for (i = 0; i < columns->n_columns; i++) { + put_json_column(json, row, columns->columns[i]); + } + return json; +} + +void +ovsdb_row_set_init(struct ovsdb_row_set *set) +{ + set->rows = NULL; + set->n_rows = set->allocated_rows = 0; +} + +void +ovsdb_row_set_destroy(struct ovsdb_row_set *set) +{ + free(set->rows); +} + +void +ovsdb_row_set_add_row(struct ovsdb_row_set *set, const struct ovsdb_row *row) +{ + if (set->n_rows >= set->allocated_rows) { + set->rows = x2nrealloc(set->rows, &set->allocated_rows, + sizeof *set->rows); + } + set->rows[set->n_rows++] = row; +} + +struct json * +ovsdb_row_set_to_json(const struct ovsdb_row_set *rows, + const struct ovsdb_column_set *columns) +{ + struct json **json_rows; + size_t i; + + json_rows = xmalloc(rows->n_rows * sizeof *json_rows); + for (i = 0; i < rows->n_rows; i++) { + json_rows[i] = ovsdb_row_to_json(rows->rows[i], columns); + } + return json_array_create(json_rows, rows->n_rows); +} + +struct ovsdb_row_set_sort_cbdata { + struct ovsdb_row_set *set; + const struct ovsdb_column_set *columns; +}; + +static int +ovsdb_row_set_sort_compare_cb(size_t a, size_t b, void *cbdata_) +{ + struct ovsdb_row_set_sort_cbdata *cbdata = cbdata_; + return ovsdb_row_compare_columns_3way(cbdata->set->rows[a], + cbdata->set->rows[b], + cbdata->columns); +} + +static void +ovsdb_row_set_sort_swap_cb(size_t a, size_t b, void *cbdata_) +{ + struct ovsdb_row_set_sort_cbdata *cbdata = cbdata_; + const struct ovsdb_row *tmp = cbdata->set->rows[a]; + cbdata->set->rows[a] = cbdata->set->rows[b]; + cbdata->set->rows[b] = tmp; +} + +void +ovsdb_row_set_sort(struct ovsdb_row_set *set, + const struct ovsdb_column_set *columns) +{ + if (columns && columns->n_columns && set->n_rows > 1) { + struct ovsdb_row_set_sort_cbdata cbdata; + cbdata.set = set; + cbdata.columns = columns; + sort(set->n_rows, + ovsdb_row_set_sort_compare_cb, + ovsdb_row_set_sort_swap_cb, + &cbdata); + } +} + +void +ovsdb_row_hash_init(struct ovsdb_row_hash *rh, + const struct ovsdb_column_set *columns) +{ + hmap_init(&rh->rows); + ovsdb_column_set_clone(&rh->columns, columns); +} + +void +ovsdb_row_hash_destroy(struct ovsdb_row_hash *rh, bool destroy_rows) +{ + struct ovsdb_row_hash_node *node, *next; + + HMAP_FOR_EACH_SAFE (node, next, struct ovsdb_row_hash_node, hmap_node, + &rh->rows) { + hmap_remove(&rh->rows, &node->hmap_node); + if (destroy_rows) { + ovsdb_row_destroy((struct ovsdb_row *) node->row); + } + free(node); + } + hmap_destroy(&rh->rows); + ovsdb_column_set_destroy(&rh->columns); +} + +size_t +ovsdb_row_hash_count(const struct ovsdb_row_hash *rh) +{ + return hmap_count(&rh->rows); +} + +bool +ovsdb_row_hash_contains(const struct ovsdb_row_hash *rh, + const struct ovsdb_row *row) +{ + size_t hash = ovsdb_row_hash_columns(row, &rh->columns, 0); + return ovsdb_row_hash_contains__(rh, row, hash); +} + +/* Returns true if every row in 'b' has an equal row in 'a'. */ +bool +ovsdb_row_hash_contains_all(const struct ovsdb_row_hash *a, + const struct ovsdb_row_hash *b) +{ + struct ovsdb_row_hash_node *node; + + assert(ovsdb_column_set_equals(&a->columns, &b->columns)); + HMAP_FOR_EACH (node, struct ovsdb_row_hash_node, hmap_node, &b->rows) { + if (!ovsdb_row_hash_contains__(a, node->row, node->hmap_node.hash)) { + return false; + } + } + return true; +} + +bool +ovsdb_row_hash_insert(struct ovsdb_row_hash *rh, const struct ovsdb_row *row) +{ + size_t hash = ovsdb_row_hash_columns(row, &rh->columns, 0); + return ovsdb_row_hash_insert__(rh, row, hash); +} + +bool +ovsdb_row_hash_contains__(const struct ovsdb_row_hash *rh, + const struct ovsdb_row *row, size_t hash) +{ + struct ovsdb_row_hash_node *node; + HMAP_FOR_EACH_WITH_HASH (node, struct ovsdb_row_hash_node, hmap_node, + hash, &rh->rows) { + if (ovsdb_row_equal_columns(row, node->row, &rh->columns)) { + return true; + } + } + return false; +} + +bool +ovsdb_row_hash_insert__(struct ovsdb_row_hash *rh, const struct ovsdb_row *row, + size_t hash) +{ + if (!ovsdb_row_hash_contains__(rh, row, hash)) { + struct ovsdb_row_hash_node *node = xmalloc(sizeof *node); + node->row = row; + hmap_insert(&rh->rows, &node->hmap_node, hash); + return true; + } else { + return false; + } +} diff --git a/ovsdb/row.h b/ovsdb/row.h new file mode 100644 index 00000000..55c4f142 --- /dev/null +++ b/ovsdb/row.h @@ -0,0 +1,139 @@ +/* Copyright (c) 2009 Nicira Networks + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OVSDB_ROW_H +#define OVSDB_ROW_H 1 + +#include +#include +#include "column.h" +#include "hmap.h" +#include "ovsdb-data.h" + +struct ovsdb_column_set; + +/* A row in a database table. */ +struct ovsdb_row { + struct ovsdb_table *table; /* Table to which this belongs. */ + struct hmap_node hmap_node; /* Element in ovsdb_table's 'rows' hmap. */ + struct ovsdb_txn_row *txn_row; /* Transaction that row is in, if any. */ + struct ovsdb_datum fields[]; +}; + +struct ovsdb_row *ovsdb_row_create(const struct ovsdb_table *); +struct ovsdb_row *ovsdb_row_clone(const struct ovsdb_row *); +void ovsdb_row_destroy(struct ovsdb_row *); + +uint32_t ovsdb_row_hash_columns(const struct ovsdb_row *, + const struct ovsdb_column_set *, + uint32_t basis); +bool ovsdb_row_equal_columns(const struct ovsdb_row *, + const struct ovsdb_row *, + const struct ovsdb_column_set *); +int ovsdb_row_compare_columns_3way(const struct ovsdb_row *, + const struct ovsdb_row *, + const struct ovsdb_column_set *); +void ovsdb_row_update_columns(struct ovsdb_row *, const struct ovsdb_row *, + const struct ovsdb_column_set *); + +struct ovsdb_error *ovsdb_row_from_json(struct ovsdb_row *, + const struct json *, + const struct ovsdb_symbol_table *, + struct ovsdb_column_set *included) + WARN_UNUSED_RESULT; +struct json *ovsdb_row_to_json(const struct ovsdb_row *, + const struct ovsdb_column_set *include); + +static inline const struct uuid * +ovsdb_row_get_uuid(const struct ovsdb_row *row) +{ + return &row->fields[OVSDB_COL_UUID].keys[0].uuid; +} + +static inline struct uuid * +ovsdb_row_get_uuid_rw(struct ovsdb_row *row) +{ + return &row->fields[OVSDB_COL_UUID].keys[0].uuid; +} + +static inline const struct uuid * +ovsdb_row_get_version(const struct ovsdb_row *row) +{ + return &row->fields[OVSDB_COL_VERSION].keys[0].uuid; +} + +static inline struct uuid * +ovsdb_row_get_version_rw(struct ovsdb_row *row) +{ + return &row->fields[OVSDB_COL_VERSION].keys[0].uuid; +} + +static inline uint32_t +ovsdb_row_hash(const struct ovsdb_row *row) +{ + return uuid_hash(ovsdb_row_get_uuid(row)); +} + +/* An unordered collection of rows. */ +struct ovsdb_row_set { + const struct ovsdb_row **rows; + size_t n_rows, allocated_rows; +}; + +#define OVSDB_ROW_SET_INITIALIZER { NULL, 0, 0 } + +void ovsdb_row_set_init(struct ovsdb_row_set *); +void ovsdb_row_set_destroy(struct ovsdb_row_set *); +void ovsdb_row_set_add_row(struct ovsdb_row_set *, const struct ovsdb_row *); + +struct json *ovsdb_row_set_to_json(const struct ovsdb_row_set *, + const struct ovsdb_column_set *); + +void ovsdb_row_set_sort(struct ovsdb_row_set *, + const struct ovsdb_column_set *); + +/* A hash table of rows. A specified set of columns is used for hashing and + * comparing rows. + * + * The row hash doesn't necessarily own its rows. They may be owned by, for + * example, an ovsdb_table. */ +struct ovsdb_row_hash { + struct hmap rows; + struct ovsdb_column_set columns; +}; + +#define OVSDB_ROW_HASH_INITIALIZER(RH) \ + { HMAP_INITIALIZER(&(RH).rows), OVSDB_COLUMN_SET_INITIALIZER } + +struct ovsdb_row_hash_node { + struct hmap_node hmap_node; + const struct ovsdb_row *row; +}; + +void ovsdb_row_hash_init(struct ovsdb_row_hash *, + const struct ovsdb_column_set *); +void ovsdb_row_hash_destroy(struct ovsdb_row_hash *, bool destroy_rows); +size_t ovsdb_row_hash_count(const struct ovsdb_row_hash *); +bool ovsdb_row_hash_contains(const struct ovsdb_row_hash *, + const struct ovsdb_row *); +bool ovsdb_row_hash_contains_all(const struct ovsdb_row_hash *, + const struct ovsdb_row_hash *); +bool ovsdb_row_hash_insert(struct ovsdb_row_hash *, const struct ovsdb_row *); +bool ovsdb_row_hash_contains__(const struct ovsdb_row_hash *, + const struct ovsdb_row *, size_t hash); +bool ovsdb_row_hash_insert__(struct ovsdb_row_hash *, + const struct ovsdb_row *, size_t hash); + +#endif /* ovsdb/row.h */ diff --git a/ovsdb/table.c b/ovsdb/table.c new file mode 100644 index 00000000..d017a6ba --- /dev/null +++ b/ovsdb/table.c @@ -0,0 +1,228 @@ +/* Copyright (c) 2009 Nicira Networks + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "table.h" + +#include + +#include "json.h" +#include "column.h" +#include "ovsdb-error.h" +#include "ovsdb-parser.h" +#include "ovsdb-types.h" +#include "row.h" + +static void +add_column(struct ovsdb_table_schema *ts, struct ovsdb_column *column) +{ + assert(!shash_find(&ts->columns, column->name)); + column->index = shash_count(&ts->columns); + shash_add(&ts->columns, column->name, column); +} + +struct ovsdb_table_schema * +ovsdb_table_schema_create(const char *name, const char *comment, bool mutable) +{ + struct ovsdb_column *uuid, *version; + struct ovsdb_table_schema *ts; + + ts = xzalloc(sizeof *ts); + ts->name = xstrdup(name); + ts->comment = comment ? xstrdup(comment) : NULL; + ts->mutable = mutable; + shash_init(&ts->columns); + + uuid = ovsdb_column_create( + "_uuid", "Unique identifier for this row.", + false, true, &ovsdb_type_uuid); + add_column(ts, uuid); + assert(uuid->index == OVSDB_COL_UUID); + + version = ovsdb_column_create( + "_version", "Unique identifier for this version of this row.", + false, false, &ovsdb_type_uuid); + add_column(ts, version); + assert(version->index == OVSDB_COL_VERSION); + + return ts; +} + +void +ovsdb_table_schema_destroy(struct ovsdb_table_schema *ts) +{ + struct shash_node *node; + + SHASH_FOR_EACH (node, &ts->columns) { + ovsdb_column_destroy(node->data); + } + shash_destroy(&ts->columns); + free(ts->comment); + free(ts->name); + free(ts); +} + +struct ovsdb_error * +ovsdb_table_schema_from_json(const struct json *json, const char *name, + struct ovsdb_table_schema **tsp) +{ + struct ovsdb_table_schema *ts; + const struct json *comment, *columns, *mutable; + struct shash_node *node; + struct ovsdb_parser parser; + struct ovsdb_error *error; + + *tsp = NULL; + + ovsdb_parser_init(&parser, json, "table schema for table %s", name); + comment = ovsdb_parser_member(&parser, "comment", OP_STRING | OP_OPTIONAL); + columns = ovsdb_parser_member(&parser, "columns", OP_OBJECT); + mutable = ovsdb_parser_member(&parser, "mutable", + OP_TRUE | OP_FALSE | OP_OPTIONAL); + error = ovsdb_parser_finish(&parser); + if (error) { + return error; + } + + if (shash_is_empty(json_object(columns))) { + return ovsdb_syntax_error(json, NULL, + "table must have at least one column"); + } + + ts = ovsdb_table_schema_create(name, + comment ? json_string(comment) : NULL, + mutable ? json_boolean(mutable) : true); + SHASH_FOR_EACH (node, json_object(columns)) { + struct ovsdb_column *column; + + if (node->name[0] == '_') { + error = ovsdb_syntax_error(json, NULL, "names beginning with " + "\"_\" are reserved"); + } else { + error = ovsdb_column_from_json(node->data, node->name, &column); + } + if (error) { + ovsdb_table_schema_destroy(ts); + return error; + } + + add_column(ts, column); + } + *tsp = ts; + return 0; +} + +struct json * +ovsdb_table_schema_to_json(const struct ovsdb_table_schema *ts) +{ + struct json *json, *columns; + struct shash_node *node; + + json = json_object_create(); + if (ts->comment) { + json_object_put_string(json, "comment", ts->comment); + } + if (!ts->mutable) { + json_object_put(json, "mutable", json_boolean_create(false)); + } + + columns = json_object_create(); + + SHASH_FOR_EACH (node, &ts->columns) { + struct ovsdb_column *column = node->data; + if (node->name[0] != '_') { + json_object_put(columns, column->name, + ovsdb_column_to_json(column)); + } + } + json_object_put(json, "columns", columns); + + return json; +} + +const struct ovsdb_column * +ovsdb_table_schema_get_column(const struct ovsdb_table_schema *ts, + const char *name) +{ + return shash_find_data(&ts->columns, name); +} + +struct ovsdb_table * +ovsdb_table_create(struct ovsdb_table_schema *ts) +{ + struct ovsdb_table *table; + + table = xmalloc(sizeof *table); + table->schema = ts; + hmap_init(&table->rows); + + return table; +} + +void +ovsdb_table_destroy(struct ovsdb_table *table) +{ + if (table) { + struct ovsdb_row *row, *next; + + HMAP_FOR_EACH_SAFE (row, next, struct ovsdb_row, hmap_node, + &table->rows) { + ovsdb_row_destroy(row); + } + hmap_destroy(&table->rows); + + ovsdb_table_schema_destroy(table->schema); + free(table); + } +} + +static const struct ovsdb_row * +ovsdb_table_get_row__(const struct ovsdb_table *table, const struct uuid *uuid, + size_t hash) +{ + struct ovsdb_row *row; + + HMAP_FOR_EACH_WITH_HASH (row, struct ovsdb_row, hmap_node, hash, + &table->rows) { + if (uuid_equals(ovsdb_row_get_uuid(row), uuid)) { + return row; + } + } + + return NULL; +} + +const struct ovsdb_row * +ovsdb_table_get_row(const struct ovsdb_table *table, const struct uuid *uuid) +{ + return ovsdb_table_get_row__(table, uuid, uuid_hash(uuid)); +} + +/* This is probably not the function you want. Use ovsdb_txn_row_modify() + * instead. */ +bool +ovsdb_table_put_row(struct ovsdb_table *table, struct ovsdb_row *row) +{ + const struct uuid *uuid = ovsdb_row_get_uuid(row); + size_t hash = uuid_hash(uuid); + + if (!ovsdb_table_get_row__(table, uuid, hash)) { + hmap_insert(&table->rows, &row->hmap_node, hash); + return true; + } else { + return false; + } +} diff --git a/ovsdb/table.h b/ovsdb/table.h new file mode 100644 index 00000000..9e36c911 --- /dev/null +++ b/ovsdb/table.h @@ -0,0 +1,63 @@ +/* Copyright (c) 2009 Nicira Networks + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OVSDB_TABLE_H +#define OVSDB_TABLE_H 1 + +#include +#include "compiler.h" +#include "hmap.h" +#include "shash.h" + +struct json; +struct uuid; + +/* Schema for a database table. */ +struct ovsdb_table_schema { + char *name; + char *comment; + bool mutable; + struct shash columns; /* Contains "struct ovsdb_column *"s. */ +}; + +struct ovsdb_table_schema *ovsdb_table_schema_create(const char *name, + const char *comment, + bool mutable); +void ovsdb_table_schema_destroy(struct ovsdb_table_schema *); + +struct ovsdb_error *ovsdb_table_schema_from_json(const struct json *, + const char *name, + struct ovsdb_table_schema **) + WARN_UNUSED_RESULT; +struct json *ovsdb_table_schema_to_json(const struct ovsdb_table_schema *); + +const struct ovsdb_column *ovsdb_table_schema_get_column( + const struct ovsdb_table_schema *, const char *name); + +/* Database table. */ + +struct ovsdb_table { + struct ovsdb_table_schema *schema; + struct hmap rows; /* Contains "struct ovsdb_row"s. */ +}; + +struct ovsdb_table *ovsdb_table_create(struct ovsdb_table_schema *); +void ovsdb_table_destroy(struct ovsdb_table *); + +const struct ovsdb_row *ovsdb_table_get_row(const struct ovsdb_table *, + const struct uuid *); +bool ovsdb_table_put_row(struct ovsdb_table *, struct ovsdb_row *); + +#endif /* ovsdb/table.h */ diff --git a/ovsdb/transaction.c b/ovsdb/transaction.c new file mode 100644 index 00000000..21a46ec7 --- /dev/null +++ b/ovsdb/transaction.c @@ -0,0 +1,444 @@ +/* Copyright (c) 2009 Nicira Networks + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "transaction.h" + +#include + +#include "hash.h" +#include "hmap.h" +#include "json.h" +#include "ovsdb-error.h" +#include "ovsdb.h" +#include "row.h" +#include "table.h" +#include "uuid.h" + +struct ovsdb_txn { + struct ovsdb *db; + struct hmap txn_tables; /* Contains "struct ovsdb_txn_table"s. */ +}; + +/* A table modified by a transaction. */ +struct ovsdb_txn_table { + struct hmap_node hmap_node; /* Element in ovsdb_txn's txn_tables hmap. */ + struct ovsdb_table *table; + struct hmap txn_rows; /* Contains "struct ovsdb_txn_row"s. */ +}; + +/* A row modified by the transaction: + * + * - A row added by a transaction will have null 'old' and non-null 'new'. + * + * - A row deleted by a transaction will have non-null 'old' and null + * 'new'. + * + * - A row modified by a transaction will have non-null 'old' and 'new'. + * + * - 'old' and 'new' both null is invalid. It would indicate that a row + * was added then deleted within a single transaction, but we instead + * handle that case by deleting the txn_row entirely. + */ +struct ovsdb_txn_row { + struct hmap_node hmap_node; /* In ovsdb_txn_table's txn_rows hmap. */ + struct ovsdb_row *old; /* The old row. */ + struct ovsdb_row *new; /* The new row. */ +}; + +static const struct uuid * +ovsdb_txn_row_get_uuid(const struct ovsdb_txn_row *txn_row) +{ + const struct ovsdb_row *row = txn_row->old ? txn_row->old : txn_row->new; + return ovsdb_row_get_uuid(row); +} + +struct ovsdb_txn * +ovsdb_txn_create(struct ovsdb *db) +{ + struct ovsdb_txn *txn = xmalloc(sizeof *txn); + txn->db = db; + hmap_init(&txn->txn_tables); + return txn; +} + +static void +ovsdb_txn_destroy(struct ovsdb_txn *txn, void (*cb)(struct ovsdb_txn_row *)) +{ + struct ovsdb_txn_table *txn_table, *next_txn_table; + + HMAP_FOR_EACH_SAFE (txn_table, next_txn_table, + struct ovsdb_txn_table, hmap_node, &txn->txn_tables) + { + struct ovsdb_txn_row *txn_row, *next_txn_row; + + HMAP_FOR_EACH_SAFE (txn_row, next_txn_row, + struct ovsdb_txn_row, hmap_node, + &txn_table->txn_rows) + { + if (txn_row->new) { + txn_row->new->txn_row = NULL; + } + cb(txn_row); + free(txn_row); + } + + hmap_destroy(&txn_table->txn_rows); + free(txn_table); + } + hmap_destroy(&txn->txn_tables); + free(txn); +} + +static void +ovsdb_txn_row_abort(struct ovsdb_txn_row *txn_row) +{ + struct ovsdb_row *old = txn_row->old; + struct ovsdb_row *new = txn_row->new; + + if (!old) { + hmap_remove(&new->table->rows, &new->hmap_node); + } else if (!new) { + hmap_insert(&old->table->rows, &old->hmap_node, ovsdb_row_hash(old)); + } else { + hmap_replace(&new->table->rows, &new->hmap_node, &old->hmap_node); + } + ovsdb_row_destroy(new); +} + +void +ovsdb_txn_abort(struct ovsdb_txn *txn) +{ + ovsdb_txn_destroy(txn, ovsdb_txn_row_abort); +} + +static void +ovsdb_txn_row_commit(struct ovsdb_txn_row *txn_row) +{ + ovsdb_row_destroy(txn_row->old); +} + +void +ovsdb_txn_commit(struct ovsdb_txn *txn) +{ + txn->db->run_triggers = true; + ovsdb_txn_destroy(txn, ovsdb_txn_row_commit); +} + +static void +put_json_column(struct json *object, const struct ovsdb_row *row, + const struct ovsdb_column *column) +{ + json_object_put(object, column->name, + ovsdb_datum_to_json(&row->fields[column->index], + &column->type)); +} + +static struct json * +ovsdb_txn_row_to_json(const struct ovsdb_txn_row *txn_row) +{ + const struct ovsdb_row *old = txn_row->old; + const struct ovsdb_row *new = txn_row->new; + struct shash_node *node; + struct json *json; + + if (!new) { + return json_null_create(); + } + + json = NULL; + SHASH_FOR_EACH (node, &new->table->schema->columns) { + struct ovsdb_column *column = node->data; + unsigned int index = column->index; + + if (index != OVSDB_COL_UUID && column->persistent + && (!old || !ovsdb_datum_equals(&old->fields[index], + &new->fields[index], + &column->type))) + { + if (!json) { + json = json_object_create(); + } + put_json_column(json, new, column); + } + } + return json; +} + +static struct json * +ovsdb_txn_table_to_json(const struct ovsdb_txn_table *txn_table) +{ + struct ovsdb_txn_row *txn_row; + struct json *txn_table_json; + + txn_table_json = NULL; + HMAP_FOR_EACH (txn_row, struct ovsdb_txn_row, hmap_node, + &txn_table->txn_rows) { + struct json *txn_row_json = ovsdb_txn_row_to_json(txn_row); + if (txn_row_json) { + char uuid[UUID_LEN + 1]; + + if (!txn_table_json) { + txn_table_json = json_object_create(); + } + + snprintf(uuid, sizeof uuid, + UUID_FMT, UUID_ARGS(ovsdb_txn_row_get_uuid(txn_row))); + json_object_put(txn_table_json, uuid, txn_row_json); + } + } + return txn_table_json; +} + +struct json * +ovsdb_txn_to_json(const struct ovsdb_txn *txn) +{ + struct ovsdb_txn_table *txn_table; + struct json *txn_json; + + txn_json = NULL; + HMAP_FOR_EACH (txn_table, struct ovsdb_txn_table, hmap_node, + &txn->txn_tables) { + struct json *txn_table_json = ovsdb_txn_table_to_json(txn_table); + if (!txn_json) { + txn_json = json_object_create(); + } + json_object_put(txn_json, txn_table->table->schema->name, + txn_table_json); + } + return txn_json; +} + +static struct ovsdb_error * +ovsdb_txn_row_from_json(struct ovsdb_txn *txn, struct ovsdb_table *table, + const struct uuid *row_uuid, struct json *json) +{ + const struct ovsdb_row *row = ovsdb_table_get_row(table, row_uuid); + if (json->type == JSON_NULL) { + if (!row) { + return ovsdb_syntax_error(NULL, NULL, "transaction deletes " + "row "UUID_FMT" that does not exist", + UUID_ARGS(row_uuid)); + } + ovsdb_txn_row_delete(txn, row); + return NULL; + } else if (row) { + return ovsdb_row_from_json(ovsdb_txn_row_modify(txn, row), + json, NULL, NULL); + } else { + struct ovsdb_error *error; + struct ovsdb_row *new; + + new = ovsdb_row_create(table); + *ovsdb_row_get_uuid_rw(new) = *row_uuid; + error = ovsdb_row_from_json(new, json, NULL, NULL); + if (error) { + ovsdb_row_destroy(new); + } + + ovsdb_txn_row_insert(txn, new); + + return error; + } +} + +static struct ovsdb_error * +ovsdb_txn_table_from_json(struct ovsdb_txn *txn, struct ovsdb_table *table, + struct json *json) +{ + struct shash_node *node; + + if (json->type != JSON_OBJECT) { + return ovsdb_syntax_error(json, NULL, "object expected"); + } + + SHASH_FOR_EACH (node, json->u.object) { + const char *uuid_string = node->name; + struct json *txn_row_json = node->data; + struct ovsdb_error *error; + struct uuid row_uuid; + + if (!uuid_from_string(&row_uuid, uuid_string)) { + return ovsdb_syntax_error(json, NULL, "\"%s\" is not a valid UUID", + uuid_string); + } + + error = ovsdb_txn_row_from_json(txn, table, &row_uuid, txn_row_json); + if (error) { + return error; + } + } + + return NULL; +} + +struct ovsdb_error * +ovsdb_txn_from_json(struct ovsdb *db, const struct json *json, + struct ovsdb_txn **txnp) +{ + struct ovsdb_error *error; + struct shash_node *node; + struct ovsdb_txn *txn; + + *txnp = NULL; + if (json->type != JSON_OBJECT) { + return ovsdb_syntax_error(json, NULL, "object expected"); + } + + txn = ovsdb_txn_create(db); + SHASH_FOR_EACH (node, json->u.object) { + const char *table_name = node->name; + struct json *txn_table_json = node->data; + struct ovsdb_table *table; + + table = shash_find_data(&db->tables, table_name); + if (!table) { + error = ovsdb_syntax_error(json, "unknown table", + "No table named %s.", table_name); + goto error; + } + + error = ovsdb_txn_table_from_json(txn, table, txn_table_json); + if (error) { + goto error; + } + } + *txnp = txn; + return NULL; + +error: + ovsdb_txn_abort(txn); + return error; +} + +static struct ovsdb_txn_table * +ovsdb_txn_get_txn_table__(struct ovsdb_txn *txn, + const struct ovsdb_table *table, + uint32_t hash) +{ + struct ovsdb_txn_table *txn_table; + + HMAP_FOR_EACH_IN_BUCKET (txn_table, struct ovsdb_txn_table, hmap_node, + hash, &txn->txn_tables) { + if (txn_table->table == table) { + return txn_table; + } + } + + return NULL; +} + +static struct ovsdb_txn_table * +ovsdb_txn_get_txn_table(struct ovsdb_txn *txn, const struct ovsdb_table *table) +{ + return ovsdb_txn_get_txn_table__(txn, table, hash_pointer(table, 0)); +} + +static struct ovsdb_txn_table * +ovsdb_txn_create_txn_table(struct ovsdb_txn *txn, + struct ovsdb_table *table) +{ + uint32_t hash = hash_pointer(table, 0); + struct ovsdb_txn_table *txn_table; + + txn_table = ovsdb_txn_get_txn_table__(txn, table, hash); + if (!txn_table) { + txn_table = xmalloc(sizeof *txn_table); + txn_table->table = table; + hmap_init(&txn_table->txn_rows); + hmap_insert(&txn->txn_tables, &txn_table->hmap_node, hash); + } + return txn_table; +} + +static struct ovsdb_txn_row * +ovsdb_txn_row_create(struct ovsdb_txn_table *txn_table, + const struct ovsdb_row *old, struct ovsdb_row *new) +{ + uint32_t hash = ovsdb_row_hash(old ? old : new); + struct ovsdb_txn_row *txn_row; + + txn_row = xmalloc(sizeof *txn_row); + txn_row->old = (struct ovsdb_row *) old; + txn_row->new = new; + hmap_insert(&txn_table->txn_rows, &txn_row->hmap_node, hash); + + return txn_row; +} + +struct ovsdb_row * +ovsdb_txn_row_modify(struct ovsdb_txn *txn, const struct ovsdb_row *ro_row_) +{ + struct ovsdb_row *ro_row = (struct ovsdb_row *) ro_row_; + + if (ro_row->txn_row) { + assert(ro_row == ro_row->txn_row->new); + return ro_row; + } else { + struct ovsdb_table *table = ro_row->table; + struct ovsdb_txn_table *txn_table; + struct ovsdb_row *rw_row; + + txn_table = ovsdb_txn_create_txn_table(txn, table); + rw_row = ovsdb_row_clone(ro_row); + 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); + + return rw_row; + } +} + +void +ovsdb_txn_row_insert(struct ovsdb_txn *txn, struct ovsdb_row *row) +{ + uint32_t hash = ovsdb_row_hash(row); + struct ovsdb_table *table = row->table; + struct ovsdb_txn_table *txn_table; + + uuid_generate(ovsdb_row_get_version_rw(row)); + + txn_table = ovsdb_txn_create_txn_table(txn, table); + row->txn_row = ovsdb_txn_row_create(txn_table, NULL, row); + hmap_insert(&table->rows, &row->hmap_node, hash); +} + +/* 'row' must be assumed destroyed upon return; the caller must not reference + * it again. */ +void +ovsdb_txn_row_delete(struct ovsdb_txn *txn, const struct ovsdb_row *row_) +{ + struct ovsdb_row *row = (struct ovsdb_row *) row_; + struct ovsdb_table *table = row->table; + struct ovsdb_txn_row *txn_row = row->txn_row; + struct ovsdb_txn_table *txn_table; + + hmap_remove(&table->rows, &row->hmap_node); + + if (!txn_row) { + txn_table = ovsdb_txn_create_txn_table(txn, table); + row->txn_row = ovsdb_txn_row_create(txn_table, row, NULL); + } else { + assert(txn_row->new == row); + if (txn_row->old) { + txn_row->new = NULL; + } else { + txn_table = ovsdb_txn_get_txn_table(txn, table); + hmap_remove(&txn_table->txn_rows, &txn_row->hmap_node); + } + ovsdb_row_destroy(row); + } +} diff --git a/ovsdb/transaction.h b/ovsdb/transaction.h new file mode 100644 index 00000000..293eaf4f --- /dev/null +++ b/ovsdb/transaction.h @@ -0,0 +1,41 @@ +/* Copyright (c) 2009 Nicira Networks + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OVSDB_TRANSACTION_H +#define OVSDB_TRANSACTION_H 1 + +#include +#include "compiler.h" + +struct ovsdb; +struct ovsdb_table; +struct uuid; + +struct ovsdb_txn *ovsdb_txn_create(struct ovsdb *); +void ovsdb_txn_abort(struct ovsdb_txn *); +void ovsdb_txn_commit(struct ovsdb_txn *); + +struct json *ovsdb_txn_to_json(const struct ovsdb_txn *); +struct ovsdb_error *ovsdb_txn_from_json(struct ovsdb *, const struct json *, + struct ovsdb_txn **) + WARN_UNUSED_RESULT; + +struct ovsdb_row *ovsdb_txn_row_modify(struct ovsdb_txn *, + const struct ovsdb_row *); + +void ovsdb_txn_row_insert(struct ovsdb_txn *, struct ovsdb_row *); +void ovsdb_txn_row_delete(struct ovsdb_txn *, const struct ovsdb_row *); + +#endif /* ovsdb/transaction.h */ diff --git a/ovsdb/trigger.c b/ovsdb/trigger.c new file mode 100644 index 00000000..1ecfdcac --- /dev/null +++ b/ovsdb/trigger.c @@ -0,0 +1,129 @@ +/* Copyright (c) 2009 Nicira Networks + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "trigger.h" + +#include +#include + +#include "json.h" +#include "jsonrpc.h" +#include "ovsdb.h" +#include "poll-loop.h" + +static bool ovsdb_trigger_try(struct ovsdb *db, struct ovsdb_trigger *, + long long int now); +static void ovsdb_trigger_complete(struct ovsdb_trigger *); + +void +ovsdb_trigger_init(struct ovsdb *db, struct ovsdb_trigger *trigger, + struct json *request, struct list *completion, + long long int now) +{ + list_push_back(&db->triggers, &trigger->node); + trigger->completion = completion; + trigger->request = request; + trigger->result = NULL; + trigger->created = now; + trigger->timeout_msec = LLONG_MAX; + ovsdb_trigger_try(db, trigger, now); +} + +void +ovsdb_trigger_destroy(struct ovsdb_trigger *trigger) +{ + list_remove(&trigger->node); + json_destroy(trigger->request); + json_destroy(trigger->result); +} + +bool +ovsdb_trigger_is_complete(const struct ovsdb_trigger *trigger) +{ + return trigger->result != NULL; +} + +struct json * +ovsdb_trigger_steal_result(struct ovsdb_trigger *trigger) +{ + struct json *result = trigger->result; + trigger->result = NULL; + return result; +} + +void +ovsdb_trigger_run(struct ovsdb *db, long long int now) +{ + struct ovsdb_trigger *t, *next; + bool run_triggers; + + run_triggers = db->run_triggers; + db->run_triggers = false; + LIST_FOR_EACH_SAFE (t, next, struct ovsdb_trigger, node, &db->triggers) { + if (run_triggers || now - t->created >= t->timeout_msec) { + ovsdb_trigger_try(db, t, now); + } + } +} + +void +ovsdb_trigger_wait(struct ovsdb *db, long long int now) +{ + if (db->run_triggers) { + poll_immediate_wake(); + } else { + long long int deadline = LLONG_MAX; + struct ovsdb_trigger *t; + + LIST_FOR_EACH (t, struct ovsdb_trigger, node, &db->triggers) { + if (t->created < LLONG_MAX - t->timeout_msec) { + long long int t_deadline = t->created + t->timeout_msec; + if (deadline > t_deadline) { + deadline = t_deadline; + if (now >= deadline) { + break; + } + } + } + } + + if (deadline < LLONG_MAX) { + poll_timer_wait(MIN(deadline - now, INT_MAX)); + } + } +} + +static bool +ovsdb_trigger_try(struct ovsdb *db, struct ovsdb_trigger *t, long long int now) +{ + t->result = ovsdb_execute(db, t->request, now - t->created, + &t->timeout_msec); + if (t->result) { + ovsdb_trigger_complete(t); + return true; + } else { + return false; + } +} + +static void +ovsdb_trigger_complete(struct ovsdb_trigger *t) +{ + assert(t->result != NULL); + list_remove(&t->node); + list_push_back(t->completion, &t->node); +} diff --git a/ovsdb/trigger.h b/ovsdb/trigger.h new file mode 100644 index 00000000..521b150b --- /dev/null +++ b/ovsdb/trigger.h @@ -0,0 +1,44 @@ +/* Copyright (c) 2009 Nicira Networks + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OVSDB_TRIGGER_H +#define OVSDB_TRIGGER_H 1 + +#include "list.h" + +struct ovsdb; + +struct ovsdb_trigger { + struct list node; /* !result: in struct ovsdb "triggers" list; + * result: in completion list. */ + struct list *completion; /* Completion list. */ + struct json *request; /* Database request. */ + struct json *result; /* Result (null if none yet). */ + long long int created; /* Time created. */ + long long int timeout_msec; /* Max wait duration. */ +}; + +void ovsdb_trigger_init(struct ovsdb *, struct ovsdb_trigger *, + struct json *request, struct list *completion, + long long int now); +void ovsdb_trigger_destroy(struct ovsdb_trigger *); + +bool ovsdb_trigger_is_complete(const struct ovsdb_trigger *); +struct json *ovsdb_trigger_steal_result(struct ovsdb_trigger *); + +void ovsdb_trigger_run(struct ovsdb *, long long int now); +void ovsdb_trigger_wait(struct ovsdb *, long long int now); + +#endif /* ovsdb/trigger.h */ diff --git a/tests/automake.mk b/tests/automake.mk index 62845112..e8a429fa 100644 --- a/tests/automake.mk +++ b/tests/automake.mk @@ -15,6 +15,18 @@ TESTSUITE_AT = \ tests/jsonrpc.at \ tests/timeval.at \ tests/lockfile.at \ + tests/ovsdb.at \ + tests/ovsdb-file.at \ + tests/ovsdb-types.at \ + tests/ovsdb-data.at \ + tests/ovsdb-column.at \ + tests/ovsdb-table.at \ + tests/ovsdb-row.at \ + tests/ovsdb-condition.at \ + tests/ovsdb-query.at \ + tests/ovsdb-transaction.at \ + tests/ovsdb-execution.at \ + tests/ovsdb-trigger.at \ tests/stp.at \ tests/ovs-vsctl.at \ tests/lcov-post.at @@ -89,6 +101,11 @@ noinst_PROGRAMS += tests/test-lockfile tests_test_lockfile_SOURCES = tests/test-lockfile.c tests_test_lockfile_LDADD = lib/libopenvswitch.a +noinst_PROGRAMS += tests/test-ovsdb +tests_test_ovsdb_SOURCES = tests/test-ovsdb.c +tests_test_ovsdb_LDADD = ovsdb/libovsdb.a lib/libopenvswitch.a +EXTRA_DIST += tests/uuidfilt.pl + noinst_PROGRAMS += tests/test-sha1 tests_test_sha1_SOURCES = tests/test-sha1.c tests_test_sha1_LDADD = lib/libopenvswitch.a diff --git a/tests/library.at b/tests/library.at index c48828e9..ffcd4b83 100644 --- a/tests/library.at +++ b/tests/library.at @@ -11,6 +11,7 @@ OVS_CHECK_LCOV([test-csum], [0], [ignore]) AT_CLEANUP AT_SETUP([test flow classifier]) +AT_KEYWORDS([slow]) OVS_CHECK_LCOV([test-classifier], [0], [ignore]) AT_CLEANUP diff --git a/tests/ovsdb-column.at b/tests/ovsdb-column.at new file mode 100644 index 00000000..03cd8dc6 --- /dev/null +++ b/tests/ovsdb-column.at @@ -0,0 +1,18 @@ +AT_BANNER([OVSDB -- columns]) + +OVSDB_CHECK_POSITIVE([ordinary column], + [[parse-column mycol '{"type": "integer"}']], + [[{"type":"integer"}]]) + +OVSDB_CHECK_POSITIVE([immutable column], + [[parse-column mycol '{"type": "real", "mutable": false}']], + [[{"mutable":false,"type":"real"}]]) + +OVSDB_CHECK_POSITIVE([ephemeral column], + [[parse-column mycol '{"type": "uuid", "ephemeral": true}']], + [[{"ephemeral":true,"type":"uuid"}]]) + +OVSDB_CHECK_POSITIVE([column with comment], + [[parse-column mycol '{"type": "boolean", + "comment": "extra information about this column"}']], + [[{"comment":"extra information about this column","type":"boolean"}]]) diff --git a/tests/ovsdb-condition.at b/tests/ovsdb-condition.at new file mode 100644 index 00000000..3715b6d4 --- /dev/null +++ b/tests/ovsdb-condition.at @@ -0,0 +1,558 @@ +AT_BANNER([OVSDB -- conditions]) + +OVSDB_CHECK_POSITIVE([null condition], + [[parse-conditions \ + '{"columns": {"name": {"type": "string"}}}' \ + '[]']], + [[[]]]) + +OVSDB_CHECK_POSITIVE([conditions on scalars], + [[parse-conditions \ + '{"columns": + {"i": {"type": "integer"}, + "r": {"type": "real"}, + "b": {"type": "boolean"}, + "s": {"type": "string"}, + "u": {"type": "uuid"}}}' \ + '[["i", "==", 0]]' \ + '[["i", "!=", 1]]' \ + '[["i", "<", 2]]' \ + '[["i", "<=", 3]]' \ + '[["i", ">", 4]]' \ + '[["i", ">=", 5]]' \ + '[["i", "includes", 6]]' \ + '[["i", "excludes", 7]]' \ + '[["r", "==", 0.5]]' \ + '[["r", "!=", 1.5]]' \ + '[["r", "<", 2.5]]' \ + '[["r", "<=", 3.5]]' \ + '[["r", ">", 4.5]]' \ + '[["r", ">=", 5.5]]' \ + '[["r", "includes", 6.5]]' \ + '[["r", "excludes", 7.5]]' \ + '[["b", "==", true]]' \ + '[["b", "!=", false]]' \ + '[["b", "includes", false]]' \ + '[["b", "excludes", true]]' \ + '[["s", "==", "a"]]' \ + '[["s", "!=", "b"]]' \ + '[["s", "includes", "c"]]' \ + '[["s", "excludes", "d"]]' \ + '[["u", "==", ["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]]]' \ + '[["u", "!=", ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]]' \ + '[["u", "includes", ["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]]]' \ + '[["u", "excludes", ["uuid", "62315898-64e0-40b9-b26f-ff74225303e6"]]]']], + [[[["i","==",0]] +[["i","!=",1]] +[["i","<",2]] +[["i","<=",3]] +[["i",">",4]] +[["i",">=",5]] +[["i","includes",6]] +[["i","excludes",7]] +[["r","==",0.5]] +[["r","!=",1.5]] +[["r","<",2.5]] +[["r","<=",3.5]] +[["r",">",4.5]] +[["r",">=",5.5]] +[["r","includes",6.5]] +[["r","excludes",7.5]] +[["b","==",true]] +[["b","!=",false]] +[["b","includes",false]] +[["b","excludes",true]] +[["s","==","a"]] +[["s","!=","b"]] +[["s","includes","c"]] +[["s","excludes","d"]] +[["u","==",["uuid","b10d28f7-af18-4a67-9e78-2a6394516c59"]]] +[["u","!=",["uuid","9179ca6d-6d65-400a-b455-3ad92783a099"]]] +[["u","includes",["uuid","ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]]] +[["u","excludes",["uuid","62315898-64e0-40b9-b26f-ff74225303e6"]]]]], + [condition]) + +AT_SETUP([disallowed conditions on scalars]) +AT_KEYWORDS([ovsdb negative condition]) +OVS_CHECK_LCOV([[test-ovsdb parse-conditions \ + '{"columns": + {"i": {"type": "integer"}, + "r": {"type": "real"}, + "b": {"type": "boolean"}, + "s": {"type": "string"}, + "u": {"type": "uuid"}}}' \ + '[["b", ">", true]]' \ + '[["b", ">=", false]]' \ + '[["b", "<", false]]' \ + '[["b", "<=", false]]' \ + '[["s", ">", "a"]]' \ + '[["s", ">=", "b"]]' \ + '[["s", "<", "c"]]' \ + '[["s", "<=", "d"]]' \ + '[["u", ">", ["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]]]' \ + '[["u", ">=", ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]]' \ + '[["u", "<", ["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]]]' \ + '[["u", "<=", ["uuid", "62315898-64e0-40b9-b26f-ff74225303e6"]]]']], + [1], [], + [[test-ovsdb: syntax "["b",">",true]": syntax error: Type mismatch: ">" operator may not be applied to column b of type boolean. +test-ovsdb: syntax "["b",">=",false]": syntax error: Type mismatch: ">=" operator may not be applied to column b of type boolean. +test-ovsdb: syntax "["b","<",false]": syntax error: Type mismatch: "<" operator may not be applied to column b of type boolean. +test-ovsdb: syntax "["b","<=",false]": syntax error: Type mismatch: "<=" operator may not be applied to column b of type boolean. +test-ovsdb: syntax "["s",">","a"]": syntax error: Type mismatch: ">" operator may not be applied to column s of type string. +test-ovsdb: syntax "["s",">=","b"]": syntax error: Type mismatch: ">=" operator may not be applied to column s of type string. +test-ovsdb: syntax "["s","<","c"]": syntax error: Type mismatch: "<" operator may not be applied to column s of type string. +test-ovsdb: syntax "["s","<=","d"]": syntax error: Type mismatch: "<=" operator may not be applied to column s of type string. +test-ovsdb: syntax "["u",">",["uuid","b10d28f7-af18-4a67-9e78-2a6394516c59"]]": syntax error: Type mismatch: ">" operator may not be applied to column u of type uuid. +test-ovsdb: syntax "["u",">=",["uuid","9179ca6d-6d65-400a-b455-3ad92783a099"]]": syntax error: Type mismatch: ">=" operator may not be applied to column u of type uuid. +test-ovsdb: syntax "["u","<",["uuid","ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]]": syntax error: Type mismatch: "<" operator may not be applied to column u of type uuid. +test-ovsdb: syntax "["u","<=",["uuid","62315898-64e0-40b9-b26f-ff74225303e6"]]": syntax error: Type mismatch: "<=" operator may not be applied to column u of type uuid. +]]) +AT_CLEANUP + +OVSDB_CHECK_POSITIVE([conditions on sets], + [[parse-conditions \ + '{"columns": + {"i": {"type": {"key": "integer", "min": 0, "max": "unlimited"}}, + "r": {"type": {"key": "real", "min": 0, "max": "unlimited"}}, + "b": {"type": {"key": "boolean", "min": 0, "max": "unlimited"}}, + "s": {"type": {"key": "string", "min": 0, "max": "unlimited"}}, + "u": {"type": {"key": "uuid", "min": 0, "max": "unlimited"}}}}' \ + '[["i", "==", ["set", []]]]' \ + '[["i", "!=", ["set", [1]]]]' \ + '[["i", "includes", ["set", [1, 2]]]]' \ + '[["i", "excludes", ["set", [1, 2, 3]]]]' \ + '[["r", "==", ["set", []]]]' \ + '[["r", "!=", ["set", [1.5]]]]' \ + '[["r", "includes", ["set", [1.5, 2.5]]]]' \ + '[["r", "excludes", ["set", [1.5, 2.5, 3.5]]]]' \ + '[["b", "==", ["set", [true]]]]' \ + '[["b", "!=", ["set", [false]]]]' \ + '[["b", "includes", ["set", [false]]]]' \ + '[["b", "excludes", ["set", [true, false]]]]' \ + '[["s", "==", ["set", ["a"]]]]' \ + '[["s", "!=", ["set", ["a", "b"]]]]' \ + '[["s", "includes", ["set", ["c"]]]]' \ + '[["s", "excludes", ["set", ["c", "d"]]]]' \ + '[["u", "==", + ["set", [["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]]]]]' \ + '[["u", "==", + ["set", [["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"], + ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]]]]' \ + '[["u", "includes", + ["set", [["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"], + ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"], + ["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]]]]]' \ + '[["u", "excludes", + ["set", [["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"], + ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"], + ["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"], + ["uuid", "62315898-64e0-40b9-b26f-ff74225303e6"]]]]]' \ +]], + [[[["i","==",["set",[]]]] +[["i","!=",["set",[1]]]] +[["i","includes",["set",[1,2]]]] +[["i","excludes",["set",[1,2,3]]]] +[["r","==",["set",[]]]] +[["r","!=",["set",[1.5]]]] +[["r","includes",["set",[1.5,2.5]]]] +[["r","excludes",["set",[1.5,2.5,3.5]]]] +[["b","==",["set",[true]]]] +[["b","!=",["set",[false]]]] +[["b","includes",["set",[false]]]] +[["b","excludes",["set",[false,true]]]] +[["s","==",["set",["a"]]]] +[["s","!=",["set",["a","b"]]]] +[["s","includes",["set",["c"]]]] +[["s","excludes",["set",["c","d"]]]] +[["u","==",["set",[["uuid","b10d28f7-af18-4a67-9e78-2a6394516c59"]]]]] +[["u","==",["set",[["uuid","9179ca6d-6d65-400a-b455-3ad92783a099"],["uuid","b10d28f7-af18-4a67-9e78-2a6394516c59"]]]]] +[["u","includes",["set",[["uuid","9179ca6d-6d65-400a-b455-3ad92783a099"],["uuid","ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"],["uuid","b10d28f7-af18-4a67-9e78-2a6394516c59"]]]]] +[["u","excludes",["set",[["uuid","62315898-64e0-40b9-b26f-ff74225303e6"],["uuid","9179ca6d-6d65-400a-b455-3ad92783a099"],["uuid","ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"],["uuid","b10d28f7-af18-4a67-9e78-2a6394516c59"]]]]]]], + [condition]) + +OVSDB_CHECK_POSITIVE([condition sorting], + [[parse-conditions \ + '{"columns": {"i": {"type": "integer"}}}' \ + '[["i", "excludes", 7], + ["i", "!=", 8], + ["i", "==", 1], + ["i", "includes", 2], + ["i", "<=", 3], + ["i", "<", 4], + ["i", ">", 6], + ["i", ">=", 5], + ["_uuid", "==", ["uuid", "d50e85c6-8ae7-4b16-b69e-4395928bd9be"]]]']], + [[[["_uuid","==",["uuid","d50e85c6-8ae7-4b16-b69e-4395928bd9be"]],["i","==",1],["i","includes",2],["i","<=",3],["i","<",4],["i",">=",5],["i",">",6],["i","excludes",7],["i","!=",8]]]]) + +OVSDB_CHECK_POSITIVE([evaluating null condition], + [[evaluate-conditions \ + '{"columns": {"i": {"type": "integer"}}}' \ + '[[]]' \ + '[{"i": 0}, + {"i": 1}, + {"i": 2}']]], + [condition 0: TTT]) + +OVSDB_CHECK_POSITIVE([evaluating conditions on integers], + [[evaluate-conditions \ + '{"columns": {"i": {"type": "integer"}}}' \ + '[[["i", "<", 1]], + [["i", "<=", 1]], + [["i", "==", 1]], + [["i", "!=", 1]], + [["i", ">=", 1]], + [["i", ">", 1]], + [["i", "includes", 1]], + [["i", "excludes", 1]]]' \ + '[{"i": 0}, + {"i": 1}, + {"i": 2}']]], + [condition 0: T-- +condition 1: TT- +condition 2: -T- +condition 3: T-T +condition 4: -TT +condition 5: --T +condition 6: -T- +condition 7: T-T], [condition]) + +OVSDB_CHECK_POSITIVE([evaluating conditions on reals], + [[evaluate-conditions \ + '{"columns": {"r": {"type": "real"}}}' \ + '[[["r", "<", 5.0]], + [["r", "<=", 5.0]], + [["r", "==", 5.0]], + [["r", "!=", 5.0]], + [["r", ">=", 5.0]], + [["r", ">", 5.0]], + [["r", "includes", 5.0]], + [["r", "excludes", 5.0]]]' \ + '[{"r": 0}, + {"r": 5.0}, + {"r": 5.1}']]], + [condition 0: T-- +condition 1: TT- +condition 2: -T- +condition 3: T-T +condition 4: -TT +condition 5: --T +condition 6: -T- +condition 7: T-T], [condition]) + +OVSDB_CHECK_POSITIVE([evaluating conditions on booleans], + [[evaluate-conditions \ + '{"columns": {"b": {"type": "boolean"}}}' \ + '[[["b", "==", true]], + [["b", "!=", true]], + [["b", "includes", true]], + [["b", "excludes", true]], + [["b", "==", false]], + [["b", "!=", false]], + [["b", "includes", false]], + [["b", "excludes", false]]]' \ + '[{"b": true}, + {"b": false}']]], + [condition 0: T- +condition 1: -T +condition 2: T- +condition 3: -T +condition 4: -T +condition 5: T- +condition 6: -T +condition 7: T-], [condition]) + +OVSDB_CHECK_POSITIVE([evaluating conditions on strings], + [[evaluate-conditions \ + '{"columns": {"s": {"type": "string"}}}' \ + '[[["s", "==", ""]], + [["s", "!=", ""]], + [["s", "includes", ""]], + [["s", "excludes", ""]], + [["s", "==", "foo"]], + [["s", "!=", "foo"]], + [["s", "includes", "foo"]], + [["s", "excludes", "foo"]]]' \ + '[{"s": ""}, + {"s": "foo"}, + {"s": "xxx"}']]], + [condition 0: T-- +condition 1: -TT +condition 2: T-- +condition 3: -TT +condition 4: -T- +condition 5: T-T +condition 6: -T- +condition 7: T-T], [condition]) + +OVSDB_CHECK_POSITIVE([evaluating conditions on UUIDs], + [[evaluate-conditions \ + '{"columns": {"u": {"type": "uuid"}}}' \ + '[[["u", "==", ["uuid", "8a1dbdb8-416f-4ce9-affa-3332691714b6"]]], + [["u", "!=", ["uuid", "8a1dbdb8-416f-4ce9-affa-3332691714b6"]]], + [["u", "includes", ["uuid", "8a1dbdb8-416f-4ce9-affa-3332691714b6"]]], + [["u", "excludes", ["uuid", "8a1dbdb8-416f-4ce9-affa-3332691714b6"]]], + [["u", "==", ["uuid", "06151f9d-62d6-4f59-8504-e9765107faa9"]]], + [["u", "!=", ["uuid", "06151f9d-62d6-4f59-8504-e9765107faa9"]]], + [["u", "includes", ["uuid", "06151f9d-62d6-4f59-8504-e9765107faa9"]]], + [["u", "excludes", ["uuid", "06151f9d-62d6-4f59-8504-e9765107faa9"]]]]' \ + '[{"u": ["uuid", "8a1dbdb8-416f-4ce9-affa-3332691714b6"]}, + {"u": ["uuid", "06151f9d-62d6-4f59-8504-e9765107faa9"]}, + {"u": ["uuid", "00000000-0000-0000-0000-000000000000"]}']]], + [condition 0: T-- +condition 1: -TT +condition 2: T-- +condition 3: -TT +condition 4: -T- +condition 5: T-T +condition 6: -T- +condition 7: T-T], [condition]) + +OVSDB_CHECK_POSITIVE([evaluating conditions on sets], + [[evaluate-conditions \ + '{"columns": {"i": {"type": {"key": "integer", "min": 0, "max": "unlimited"}}}}' \ + '[[["i", "==", ["set", []]]], + [["i", "==", ["set", [0]]]], + [["i", "==", ["set", [1]]]], + [["i", "==", ["set", [0, 1]]]], + [["i", "==", ["set", [2]]]], + [["i", "==", ["set", [2, 0]]]], + [["i", "==", ["set", [2, 1]]]], + [["i", "==", ["set", [2, 1, 0]]]], + [["i", "!=", ["set", []]]], + [["i", "!=", ["set", [0]]]], + [["i", "!=", ["set", [1]]]], + [["i", "!=", ["set", [0, 1]]]], + [["i", "!=", ["set", [2]]]], + [["i", "!=", ["set", [2, 0]]]], + [["i", "!=", ["set", [2, 1]]]], + [["i", "!=", ["set", [2, 1, 0]]]], + [["i", "includes", ["set", []]]], + [["i", "includes", ["set", [0]]]], + [["i", "includes", ["set", [1]]]], + [["i", "includes", ["set", [0, 1]]]], + [["i", "includes", ["set", [2]]]], + [["i", "includes", ["set", [2, 0]]]], + [["i", "includes", ["set", [2, 1]]]], + [["i", "includes", ["set", [2, 1, 0]]]], + [["i", "excludes", ["set", []]]], + [["i", "excludes", ["set", [0]]]], + [["i", "excludes", ["set", [1]]]], + [["i", "excludes", ["set", [0, 1]]]], + [["i", "excludes", ["set", [2]]]], + [["i", "excludes", ["set", [2, 0]]]], + [["i", "excludes", ["set", [2, 1]]]], + [["i", "excludes", ["set", [2, 1, 0]]]]]' \ + '[{"i": ["set", []]}, + {"i": ["set", [0]]}, + {"i": ["set", [1]]}, + {"i": ["set", [0, 1]]}, + {"i": ["set", [2]]}, + {"i": ["set", [2, 0]]}, + {"i": ["set", [2, 1]]}, + {"i": ["set", [2, 1, 0]]}]']], + [dnl +condition 0: T---- --- +condition 1: -T--- --- +condition 2: --T-- --- +condition 3: ---T- --- +condition 4: ----T --- +condition 5: ----- T-- +condition 6: ----- -T- +condition 7: ----- --T +condition 8: -TTTT TTT +condition 9: T-TTT TTT +condition 10: TT-TT TTT +condition 11: TTT-T TTT +condition 12: TTTT- TTT +condition 13: TTTTT -TT +condition 14: TTTTT T-T +condition 15: TTTTT TT- +condition 16: TTTTT TTT +condition 17: -T-T- T-T +condition 18: --TT- -TT +condition 19: ---T- --T +condition 20: ----T TTT +condition 21: ----- T-T +condition 22: ----- -TT +condition 23: ----- --T +condition 24: TTTTT TTT +condition 25: T-T-T -T- +condition 26: TT--T T-- +condition 27: T---T --- +condition 28: TTTT- --- +condition 29: T-T-- --- +condition 30: TT--- --- +condition 31: T---- ---], [condition]) + +# This is the same as the "set" test except that it adds values, +# all of which always match. +OVSDB_CHECK_POSITIVE([evaluating conditions on maps (1)], + [[evaluate-conditions \ + '{"columns": {"i": {"type": {"key": "integer", + "value": "boolean", + "min": 0, + "max": "unlimited"}}}}' \ + '[[["i", "==", ["map", []]]], + [["i", "==", ["map", [[0, true]]]]], + [["i", "==", ["map", [[1, false]]]]], + [["i", "==", ["map", [[0, true], [1, false]]]]], + [["i", "==", ["map", [[2, true]]]]], + [["i", "==", ["map", [[2, true], [0, true]]]]], + [["i", "==", ["map", [[2, true], [1, false]]]]], + [["i", "==", ["map", [[2, true], [1, false], [0, true]]]]], + [["i", "!=", ["map", []]]], + [["i", "!=", ["map", [[0, true]]]]], + [["i", "!=", ["map", [[1, false]]]]], + [["i", "!=", ["map", [[0, true], [1, false]]]]], + [["i", "!=", ["map", [[2, true]]]]], + [["i", "!=", ["map", [[2, true], [0, true]]]]], + [["i", "!=", ["map", [[2, true], [1, false]]]]], + [["i", "!=", ["map", [[2, true], [1, false], [0, true]]]]], + [["i", "includes", ["map", []]]], + [["i", "includes", ["map", [[0, true]]]]], + [["i", "includes", ["map", [[1, false]]]]], + [["i", "includes", ["map", [[0, true], [1, false]]]]], + [["i", "includes", ["map", [[2, true]]]]], + [["i", "includes", ["map", [[2, true], [0, true]]]]], + [["i", "includes", ["map", [[2, true], [1, false]]]]], + [["i", "includes", ["map", [[2, true], [1, false], [0, true]]]]], + [["i", "excludes", ["map", []]]], + [["i", "excludes", ["map", [[0, true]]]]], + [["i", "excludes", ["map", [[1, false]]]]], + [["i", "excludes", ["map", [[0, true], [1, false]]]]], + [["i", "excludes", ["map", [[2, true]]]]], + [["i", "excludes", ["map", [[2, true], [0, true]]]]], + [["i", "excludes", ["map", [[2, true], [1, false]]]]], + [["i", "excludes", ["map", [[2, true], [1, false], [0, true]]]]]]' \ + '[{"i": ["map", []]}, + {"i": ["map", [[0, true]]]}, + {"i": ["map", [[1, false]]]}, + {"i": ["map", [[0, true], [1, false]]]}, + {"i": ["map", [[2, true]]]}, + {"i": ["map", [[2, true], [0, true]]]}, + {"i": ["map", [[2, true], [1, false]]]}, + {"i": ["map", [[2, true], [1, false], [0, true]]]}]']], + [dnl +condition 0: T---- --- +condition 1: -T--- --- +condition 2: --T-- --- +condition 3: ---T- --- +condition 4: ----T --- +condition 5: ----- T-- +condition 6: ----- -T- +condition 7: ----- --T +condition 8: -TTTT TTT +condition 9: T-TTT TTT +condition 10: TT-TT TTT +condition 11: TTT-T TTT +condition 12: TTTT- TTT +condition 13: TTTTT -TT +condition 14: TTTTT T-T +condition 15: TTTTT TT- +condition 16: TTTTT TTT +condition 17: -T-T- T-T +condition 18: --TT- -TT +condition 19: ---T- --T +condition 20: ----T TTT +condition 21: ----- T-T +condition 22: ----- -TT +condition 23: ----- --T +condition 24: TTTTT TTT +condition 25: T-T-T -T- +condition 26: TT--T T-- +condition 27: T---T --- +condition 28: TTTT- --- +condition 29: T-T-- --- +condition 30: TT--- --- +condition 31: T---- ---], [condition]) + +# This is the same as the "set" test except that it adds values, +# and those values don't always match. +OVSDB_CHECK_POSITIVE([evaluating conditions on maps (2)], + [[evaluate-conditions \ + '{"columns": {"i": {"type": {"key": "integer", + "value": "boolean", + "min": 0, + "max": "unlimited"}}}}' \ + '[[["i", "==", ["map", []]]], + [["i", "==", ["map", [[0, true]]]]], + [["i", "==", ["map", [[1, false]]]]], + [["i", "==", ["map", [[0, true], [1, false]]]]], + [["i", "==", ["map", [[2, true]]]]], + [["i", "==", ["map", [[2, true], [0, true]]]]], + [["i", "==", ["map", [[2, true], [1, false]]]]], + [["i", "==", ["map", [[2, true], [1, false], [0, true]]]]], + [["i", "!=", ["map", []]]], + [["i", "!=", ["map", [[0, true]]]]], + [["i", "!=", ["map", [[1, false]]]]], + [["i", "!=", ["map", [[0, true], [1, false]]]]], + [["i", "!=", ["map", [[2, true]]]]], + [["i", "!=", ["map", [[2, true], [0, true]]]]], + [["i", "!=", ["map", [[2, true], [1, false]]]]], + [["i", "!=", ["map", [[2, true], [1, false], [0, true]]]]], + [["i", "includes", ["map", []]]], + [["i", "includes", ["map", [[0, true]]]]], + [["i", "includes", ["map", [[1, false]]]]], + [["i", "includes", ["map", [[0, true], [1, false]]]]], + [["i", "includes", ["map", [[2, true]]]]], + [["i", "includes", ["map", [[2, true], [0, true]]]]], + [["i", "includes", ["map", [[2, true], [1, false]]]]], + [["i", "includes", ["map", [[2, true], [1, false], [0, true]]]]], + [["i", "excludes", ["map", []]]], + [["i", "excludes", ["map", [[0, true]]]]], + [["i", "excludes", ["map", [[1, false]]]]], + [["i", "excludes", ["map", [[0, true], [1, false]]]]], + [["i", "excludes", ["map", [[2, true]]]]], + [["i", "excludes", ["map", [[2, true], [0, true]]]]], + [["i", "excludes", ["map", [[2, true], [1, false]]]]], + [["i", "excludes", ["map", [[2, true], [1, false], [0, true]]]]]]' \ + '[{"i": ["map", []]}, + {"i": ["map", [[0, true]]]}, + {"i": ["map", [[0, false]]]}, + {"i": ["map", [[1, false]]]}, + {"i": ["map", [[1, true]]]}, + + {"i": ["map", [[0, true], [1, false]]]}, + {"i": ["map", [[0, true], [1, true]]]}, + {"i": ["map", [[2, true]]]}, + {"i": ["map", [[2, false]]]}, + {"i": ["map", [[2, true], [0, true]]]}, + + {"i": ["map", [[2, false], [0, true]]]}, + {"i": ["map", [[2, true], [1, false]]]}, + {"i": ["map", [[2, true], [1, true]]]}, + {"i": ["map", [[2, true], [1, false], [0, true]]]}, + {"i": ["map", [[2, true], [1, false], [0, false]]]}]']], + [dnl +condition 0: T---- ----- ----- +condition 1: -T--- ----- ----- +condition 2: ---T- ----- ----- +condition 3: ----- T---- ----- +condition 4: ----- --T-- ----- +condition 5: ----- ----T ----- +condition 6: ----- ----- -T--- +condition 7: ----- ----- ---T- +condition 8: -TTTT TTTTT TTTTT +condition 9: T-TTT TTTTT TTTTT +condition 10: TTT-T TTTTT TTTTT +condition 11: TTTTT -TTTT TTTTT +condition 12: TTTTT TT-TT TTTTT +condition 13: TTTTT TTTT- TTTTT +condition 14: TTTTT TTTTT T-TTT +condition 15: TTTTT TTTTT TTT-T +condition 16: TTTTT TTTTT TTTTT +condition 17: -T--- TT--T T--T- +condition 18: ---T- T---- -T-TT +condition 19: ----- T---- ---T- +condition 20: ----- --T-T -TTTT +condition 21: ----- ----T ---T- +condition 22: ----- ----- -T-TT +condition 23: ----- ----- ---T- +condition 24: TTTTT TTTTT TTTTT +condition 25: T-TTT --TT- -TT-T +condition 26: TTT-T -TTTT T-T-- +condition 27: T-T-T --TT- --T-- +condition 28: TTTTT TT-T- T---- +condition 29: T-TTT ---T- ----- +condition 30: TTT-T -T-T- T---- +condition 31: T-T-T ---T- -----], [condition]) diff --git a/tests/ovsdb-data.at b/tests/ovsdb-data.at new file mode 100644 index 00000000..bedaf700 --- /dev/null +++ b/tests/ovsdb-data.at @@ -0,0 +1,259 @@ +AT_BANNER([OVSDB -- atoms]) + +OVSDB_CHECK_POSITIVE([integer atom], + [[parse-atoms '["integer"]' \ + '[0]' \ + '[-1]' \ + '[1e3]' \ + '[9223372036854775807]' \ + '[-9223372036854775808]' ]], + [0 +-1 +1000 +9223372036854775807 +-9223372036854775808]) + +OVSDB_CHECK_POSITIVE([real atom], + [[parse-atoms '["real"]' \ + '[0]' \ + '[0.0]' \ + '[-0.0]' \ + '[-1.25]' \ + '[1e3]' \ + '[1e37]' \ + '[0.00390625]' ]], + [0 +0 +0 +-1.25 +1000 +1e+37 +0.00390625]) + +OVSDB_CHECK_POSITIVE([boolean atom], + [[parse-atoms '["boolean"]' '[true]' '[false]' ]], + [true +false]) + +OVSDB_CHECK_POSITIVE([string atom], + [[parse-atoms '["string"]' '[""]' '["true"]' '["\"\\\/\b\f\n\r\t"]']], + ["" +"true" +"\"\\/\b\f\n\r\t"]) + +OVSDB_CHECK_POSITIVE([uuid atom], + [[parse-atoms '["uuid"]' '["uuid", "550e8400-e29b-41d4-a716-446655440000"]']], + [[["uuid","550e8400-e29b-41d4-a716-446655440000"]]]) + +OVSDB_CHECK_POSITIVE([integer atom sorting], + [[sort-atoms '["integer"]' '[55,0,-1,2,1]']], + [[[-1,0,1,2,55]]]) + +OVSDB_CHECK_POSITIVE([real atom sorting], + [[sort-atoms '["real"]' '[1.25,1.23,0.0,-0.0,-1e99]']], + [[[-1e+99,0,0,1.23,1.25]]]) + +OVSDB_CHECK_POSITIVE([boolean atom sorting], + [[sort-atoms '["boolean"]' '[true,false,true,false,false]']], + [[[false,false,false,true,true]]]) + +OVSDB_CHECK_POSITIVE([string atom sorting], + [[sort-atoms '["string"]' '["abd","abc","\b","xxx"]']], + [[["\b","abc","abd","xxx"]]]) + +OVSDB_CHECK_POSITIVE([uuid atom sorting], + [[sort-atoms '["uuid"]' '[ + ["uuid", "00000000-0000-0000-0000-000000000001"], + ["uuid", "00000000-1000-0000-0000-000000000000"], + ["uuid", "00000000-0000-1000-0000-000000000000"], + ["uuid", "00010000-0000-0000-0000-000000000000"], + ["uuid", "00000000-0000-0000-0000-000000000100"], + ["uuid", "00000000-0000-0000-0000-000100000000"], + ["uuid", "00000000-0000-0010-0000-000000000000"], + ["uuid", "00000100-0000-0000-0000-000000000000"], + ["uuid", "00000000-0000-0001-0000-000000000000"], + ["uuid", "00000000-0000-0000-0000-000001000000"], + ["uuid", "01000000-0000-0000-0000-000000000000"], + ["uuid", "00000000-0000-0000-0000-000000001000"], + ["uuid", "00000000-0000-0000-0000-000010000000"], + ["uuid", "00000000-0000-0000-0000-010000000000"], + ["uuid", "00000000-0000-0100-0000-000000000000"], + ["uuid", "10000000-0000-0000-0000-000000000000"], + ["uuid", "00000000-0000-0000-0000-000000000010"], + ["uuid", "00000000-0100-0000-0000-000000000000"], + ["uuid", "00000000-0000-0000-0100-000000000000"], + ["uuid", "00000000-0000-0000-0001-000000000000"], + ["uuid", "00000010-0000-0000-0000-000000000000"], + ["uuid", "00000000-0000-0000-0010-000000000000"], + ["uuid", "00000000-0000-0000-0000-000000010000"], + ["uuid", "00000000-0000-0000-1000-000000000000"], + ["uuid", "00000000-0000-0000-0000-100000000000"], + ["uuid", "00000000-0000-0000-0000-001000000000"], + ["uuid", "00000000-0000-0000-0000-000000100000"], + ["uuid", "00000000-0000-0000-0000-000000000000"], + ["uuid", "00000000-0010-0000-0000-000000000000"], + ["uuid", "00100000-0000-0000-0000-000000000000"], + ["uuid", "00000000-0001-0000-0000-000000000000"], + ["uuid", "00000001-0000-0000-0000-000000000000"], + ["uuid", "00001000-0000-0000-0000-000000000000"]]']], + [[[["uuid","00000000-0000-0000-0000-000000000000"],["uuid","00000000-0000-0000-0000-000000000001"],["uuid","00000000-0000-0000-0000-000000000010"],["uuid","00000000-0000-0000-0000-000000000100"],["uuid","00000000-0000-0000-0000-000000001000"],["uuid","00000000-0000-0000-0000-000000010000"],["uuid","00000000-0000-0000-0000-000000100000"],["uuid","00000000-0000-0000-0000-000001000000"],["uuid","00000000-0000-0000-0000-000010000000"],["uuid","00000000-0000-0000-0000-000100000000"],["uuid","00000000-0000-0000-0000-001000000000"],["uuid","00000000-0000-0000-0000-010000000000"],["uuid","00000000-0000-0000-0000-100000000000"],["uuid","00000000-0000-0000-0001-000000000000"],["uuid","00000000-0000-0000-0010-000000000000"],["uuid","00000000-0000-0000-0100-000000000000"],["uuid","00000000-0000-0000-1000-000000000000"],["uuid","00000000-0000-0001-0000-000000000000"],["uuid","00000000-0000-0010-0000-000000000000"],["uuid","00000000-0000-0100-0000-000000000000"],["uuid","00000000-0000-1000-0000-000000000000"],["uuid","00000000-0001-0000-0000-000000000000"],["uuid","00000000-0010-0000-0000-000000000000"],["uuid","00000000-0100-0000-0000-000000000000"],["uuid","00000000-1000-0000-0000-000000000000"],["uuid","00000001-0000-0000-0000-000000000000"],["uuid","00000010-0000-0000-0000-000000000000"],["uuid","00000100-0000-0000-0000-000000000000"],["uuid","00001000-0000-0000-0000-000000000000"],["uuid","00010000-0000-0000-0000-000000000000"],["uuid","00100000-0000-0000-0000-000000000000"],["uuid","01000000-0000-0000-0000-000000000000"],["uuid","10000000-0000-0000-0000-000000000000"]]]]) + +OVSDB_CHECK_NEGATIVE([real not acceptable integer atom], + [[parse-atoms '["integer"]' '[0.5]' ]], + [expected integer]) + +OVSDB_CHECK_NEGATIVE([string "true" not acceptable boolean atom], + [[parse-atoms '["boolean"]' '["true"]' ]], + [expected boolean]) + +OVSDB_CHECK_NEGATIVE([integer not acceptable string atom], + [[parse-atoms '["string"]' '[1]']], + [expected string]) + +OVSDB_CHECK_NEGATIVE([uuid atom must be expressed as array], + [[parse-atoms '["uuid"]' '["550e8400-e29b-41d4-a716-446655440000"]']], + [[expected ["uuid", ]]]) + +AT_BANNER([OSVDB -- simple data]) + +OVSDB_CHECK_POSITIVE([integer datum], + [[parse-data '["integer"]' '[0]' '[1]' '[-1]']], + [0 +1 +-1]) + +OVSDB_CHECK_POSITIVE([real datum], + [[parse-data '["real"]' '[0]' '[1.0]' '[-1.25]']], + [0 +1 +-1.25]) + +OVSDB_CHECK_POSITIVE([boolean datum], + [[parse-data '["boolean"]' '[true]' '[false]' ]], + [true +false]) + +OVSDB_CHECK_POSITIVE([string datum], + [[parse-data '["string"]' '[""]' '["true"]' '["\"\\\/\b\f\n\r\t"]']], + ["" +"true" +"\"\\/\b\f\n\r\t"]) + +AT_BANNER([OVSDB -- set data]) + +OVSDB_CHECK_POSITIVE([optional boolean], + [[parse-data '{"key": "boolean", "min": 0}' \ + '["set", [true]]' \ + '["set", [false]]' \ + '["set", []]']], + [[["set",[true]] +["set",[false]] +["set",[]]]], + [set]) + +OVSDB_CHECK_POSITIVE([set of 0 or more integers], + [[parse-data '{"key": "integer", "min": 0, "max": "unlimited"}' \ + '["set", [0]]' \ + '["set", [0, 1]]' \ + '["set", [0, 1, 2]]' \ + '["set", [0, 1, 2, 3, 4, 5]]' \ + '["set", [0, 1, 2, 3, 4, 5, 6, 7, 8]]' \ + '["set", [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]]']], + [[["set",[0]] +["set",[0,1]] +["set",[0,1,2]] +["set",[0,1,2,3,4,5]] +["set",[0,1,2,3,4,5,6,7,8]] +["set",[0,1,2,3,4,5,6,7,8,9,10]]]]) + +OVSDB_CHECK_POSITIVE([set of 1 to 3 uuids], + [[parse-data '{"key": "uuid", "min": 1, "max": 3}' \ + '["set", [["uuid", "550e8400-e29b-41d4-a716-446655440000"]]]' \ + '["set", [["uuid", "c5051240-30ff-43ed-b4b9-93cf3f050813"], + ["uuid", "90558331-09af-4d2f-a572-509cad2e9088"], + ["uuid", "550e8400-e29b-41d4-a716-446655440000"]]]']], + [[["set",[["uuid","550e8400-e29b-41d4-a716-446655440000"]]] +["set",[["uuid","550e8400-e29b-41d4-a716-446655440000"],["uuid","90558331-09af-4d2f-a572-509cad2e9088"],["uuid","c5051240-30ff-43ed-b4b9-93cf3f050813"]]]]]) + +OVSDB_CHECK_POSITIVE([set of 0 to 3 strings], + [[parse-data '{"key": "string", "min": 0, "max": 3}' \ + '["set", []]' \ + '["set", ["a relatively long string"]]' \ + '["set", ["short string", "a relatively long string"]]' \ + '["set", ["zzz", "short string", "a relatively long string"]]']], + [[["set",[]] +["set",["a relatively long string"]] +["set",["a relatively long string","short string"]] +["set",["a relatively long string","short string","zzz"]]]]) + +OVSDB_CHECK_NEGATIVE([duplicate boolean not allowed in set], + [[parse-data '{"key": "boolean", "max": 5}' '["set", [true, true]]']], + [ovsdb error: set contains duplicate]) + +OVSDB_CHECK_NEGATIVE([duplicate integer not allowed in set], + [[parse-data '{"key": "integer", "max": 5}' '["set", [1, 2, 3, 1]]']], + [ovsdb error: set contains duplicate]) + +OVSDB_CHECK_NEGATIVE([duplicate real not allowed in set], + [[parse-data '{"key": "real", "max": 5}' '["set", [0.0, -0.0]]']], + [ovsdb error: set contains duplicate]) + +OVSDB_CHECK_NEGATIVE([duplicate string not allowed in set], + [[parse-data '{"key": "string", "max": 5}' '["set", ["asdf", "ASDF", "asdf"]]']], + [ovsdb error: set contains duplicate]) + +OVSDB_CHECK_NEGATIVE([duplicate uuid not allowed in set], + [[parse-data '{"key": "uuid", "max": 5}' \ + '["set", [["uuid", "7ef21525-0088-4a28-a418-5518413e43ea"], + ["uuid", "355ad037-f1da-40aa-b47c-ff9c7e8c6a38"], + ["uuid", "7ef21525-0088-4a28-a418-5518413e43ea"]]]']], + [ovsdb error: set contains duplicate]) + +AT_BANNER([OVSDB -- map data]) + +OVSDB_CHECK_POSITIVE([map of 1 integer to boolean], + [[parse-data '{"key": "integer", "value": "boolean"}' \ + '["map", [[1, true]]]']], + [[["map",[[1,true]]]]]) + +OVSDB_CHECK_POSITIVE([map of at least 1 integer to boolean], + [[parse-data '{"key": "integer", "value": "boolean", "max": "unlimited"}' \ + '["map", [[1, true]]]' \ + '["map", [[0, true], [1, false], [2, true], [3, true], [4, true]]]' \ + '["map", [[3, false], [0, true], [4, false]]]']], + [[["map",[[1,true]]] +["map",[[0,true],[1,false],[2,true],[3,true],[4,true]]] +["map",[[0,true],[3,false],[4,false]]]]]) + +OVSDB_CHECK_POSITIVE([map of 1 boolean to integer], + [[parse-data '{"key": "boolean", "value": "integer"}' \ + '["map", [[true, 1]]]']], + [[["map",[[true,1]]]]]) + +OVSDB_CHECK_POSITIVE([map of 5 uuid to real], + [[parse-data '{"key": "uuid", "value": "real", "min": 5, "max": 5}' \ + '["map", [[["uuid", "cad8542b-6ee1-486b-971b-7dcbf6e14979"], 1.0], + [["uuid", "6b94b968-2702-4f64-9457-314a34d69b8c"], 2.0], + [["uuid", "d2c4a168-24de-47eb-a8a3-c1abfc814979"], 3.0], + [["uuid", "25bfa475-d072-4f60-8be1-00f48643e9cb"], 4.0], + [["uuid", "1c92b8ca-d5e4-4628-a85d-1dc2d099a99a"], 5.0]]]']], + [[["map",[[["uuid","1c92b8ca-d5e4-4628-a85d-1dc2d099a99a"],5],[["uuid","25bfa475-d072-4f60-8be1-00f48643e9cb"],4],[["uuid","6b94b968-2702-4f64-9457-314a34d69b8c"],2],[["uuid","cad8542b-6ee1-486b-971b-7dcbf6e14979"],1],[["uuid","d2c4a168-24de-47eb-a8a3-c1abfc814979"],3]]]]]) + +OVSDB_CHECK_POSITIVE([map of 10 string to string], + [[parse-data '{"key": "string", "value": "string", "min": 10, "max": 10}' \ + '["map", [["2 gills", "1 chopin"], + ["2 chopins", "1 pint"], + ["2 pints", "1 quart"], + ["2 quarts", "1 pottle"], + ["2 pottles", "1 gallon"], + ["2 gallons", "1 peck"], + ["2 pecks", "1 demibushel"], + ["2 demibushel", "1 firkin"], + ["2 firkins", "1 kilderkin"], + ["2 kilderkins", "1 barrel"]]]']], + [[["map",[["2 chopins","1 pint"],["2 demibushel","1 firkin"],["2 firkins","1 kilderkin"],["2 gallons","1 peck"],["2 gills","1 chopin"],["2 kilderkins","1 barrel"],["2 pecks","1 demibushel"],["2 pints","1 quart"],["2 pottles","1 gallon"],["2 quarts","1 pottle"]]]]]) + +OVSDB_CHECK_NEGATIVE([duplicate integer key not allowed in map], + [[parse-data '{"key": "integer", "value": "boolean", "max": 5}' \ + '["map", [[1, true], [2, false], [1, false]]]']], + [ovsdb error: map contains duplicate key]) diff --git a/tests/ovsdb-execution.at b/tests/ovsdb-execution.at new file mode 100644 index 00000000..e6292980 --- /dev/null +++ b/tests/ovsdb-execution.at @@ -0,0 +1,321 @@ +AT_BANNER([OVSDB -- execution]) + +m4_define([ORDINAL_SCHEMA], + [['{"name": "mydb", + "tables": { + "ordinals": { + "columns": { + "number": {"type": "integer"}, + "name": {"type": "string"}}}}}']]) + +# This is like OVSDB_CHECK_POSITIVE, except that UUIDs in the output +# are replaced by markers of the form where N is a number. The +# first unique UUID is replaced by <0>, the next by <1>, and so on. +# If a given UUID appears more than once it is always replaced by the +# same marker. +m4_define([OVSDB_CHECK_EXECUTION], + [AT_SETUP([$1]) + AT_KEYWORDS([ovsdb execute execution positive $4]) + OVS_CHECK_LCOV([test-ovsdb execute $2], [0], [stdout], []) + AT_CHECK([perl $srcdir/uuidfilt.pl stdout], [0], [$3]) + AT_CLEANUP]) + +OVSDB_CHECK_EXECUTION([insert row, query table], + [ORDINAL_SCHEMA [\ + '[{"op": "insert", + "table": "ordinals", + "row": {"number": 0, "name": "zero"}}]' \ + '[{"op": "select", + "table": "ordinals", + "where": []}]']], + [[[{"uuid":["uuid","<0>"]}] +[{"rows":[{"_uuid":["uuid","<0>"],"_version":["uuid","<1>"],"name":"zero","number":0}]}] +]]) + +OVSDB_CHECK_EXECUTION([insert rows, query by value], + [ORDINAL_SCHEMA [\ + '[{"op": "insert", + "table": "ordinals", + "row": {"number": 0, "name": "zero"}}]' \ + '[{"op": "insert", + "table": "ordinals", + "row": {"number": 1, "name": "one"}}]' \ + '[{"op": "select", + "table": "ordinals", + "where": [["name", "==", "zero"]]}]'\ + '[{"op": "select", + "table": "ordinals", + "where": [["name", "==", "one"]]}]']], + [[[{"uuid":["uuid","<0>"]}] +[{"uuid":["uuid","<1>"]}] +[{"rows":[{"_uuid":["uuid","<0>"],"_version":["uuid","<2>"],"name":"zero","number":0}]}] +[{"rows":[{"_uuid":["uuid","<1>"],"_version":["uuid","<3>"],"name":"one","number":1}]}] +]]) + +OVSDB_CHECK_EXECUTION([insert rows, query by named-uuid], + [ORDINAL_SCHEMA [\ + '[{"op": "insert", + "table": "ordinals", + "row": {"number": 0, "name": "zero"}, + "uuid-name": "first"}, + {"op": "insert", + "table": "ordinals", + "row": {"number": 1, "name": "one"}, + "uuid-name": "second"}, + {"op": "select", + "table": "ordinals", + "where": [["_uuid", "==", ["named-uuid", "first"]]]}, + {"op": "select", + "table": "ordinals", + "where": [["_uuid", "==", ["named-uuid", "second"]]]}]']], + [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"rows":[{"_uuid":["uuid","<0>"],"_version":["uuid","<2>"],"name":"zero","number":0}]},{"rows":[{"_uuid":["uuid","<1>"],"_version":["uuid","<3>"],"name":"one","number":1}]}] +]]) + +OVSDB_CHECK_EXECUTION([insert rows, update rows by value], + [ORDINAL_SCHEMA [\ + '[{"op": "insert", + "table": "ordinals", + "row": {"number": 0, "name": "zero"}, + "uuid-name": "first"}]' \ + '[{"op": "insert", + "table": "ordinals", + "row": {"number": 1, "name": "one"}, + "uuid-name": "first"}]' \ + '[{"op": "update", + "table": "ordinals", + "where": [["name", "==", "zero"]], + "row": {"name": "nought"}}]' \ + '[{"op": "select", + "table": "ordinals", + "where": [], + "sort": ["number"]}]']], + [[[{"uuid":["uuid","<0>"]}] +[{"uuid":["uuid","<1>"]}] +[{"count":1}] +[{"rows":[{"_uuid":["uuid","<0>"],"_version":["uuid","<2>"],"name":"nought","number":0},{"_uuid":["uuid","<1>"],"_version":["uuid","<3>"],"name":"one","number":1}]}] +]]) + +OVSDB_CHECK_EXECUTION([insert rows, delete by named-uuid], + [ORDINAL_SCHEMA [\ + '[{"op": "insert", + "table": "ordinals", + "row": {"number": 0, "name": "zero"}, + "uuid-name": "first"}, + {"op": "insert", + "table": "ordinals", + "row": {"number": 1, "name": "one"}, + "uuid-name": "second"}, + {"op": "delete", + "table": "ordinals", + "where": [["_uuid", "==", ["named-uuid", "first"]]]}, + {"op": "select", + "table": "ordinals", + "where": [], + "columns": ["name","number"]}]']], + [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"count":1},{"rows":[{"name":"one","number":1}]}] +]]) + +OVSDB_CHECK_EXECUTION([insert rows, delete rows by value], + [ORDINAL_SCHEMA [\ + '[{"op": "insert", + "table": "ordinals", + "row": {"number": 0, "name": "zero"}, + "uuid-name": "first"}]' \ + '[{"op": "insert", + "table": "ordinals", + "row": {"number": 1, "name": "one"}, + "uuid-name": "first"}]' \ + '[{"op": "delete", + "table": "ordinals", + "where": [["name", "==", "zero"]]}]' \ + '[{"op": "select", + "table": "ordinals", + "where": []}]']], + [[[{"uuid":["uuid","<0>"]}] +[{"uuid":["uuid","<1>"]}] +[{"count":1}] +[{"rows":[{"_uuid":["uuid","<1>"],"_version":["uuid","<2>"],"name":"one","number":1}]}] +]]) + +OVSDB_CHECK_EXECUTION([insert rows, delete by (non-matching) value], + [ORDINAL_SCHEMA [\ + '[{"op": "insert", + "table": "ordinals", + "row": {"number": 0, "name": "zero"}, + "uuid-name": "first"}]' \ + '[{"op": "insert", + "table": "ordinals", + "row": {"number": 1, "name": "one"}, + "uuid-name": "first"}]' \ + '[{"op": "delete", + "table": "ordinals", + "where": [["name", "==", "nought"]]}]' \ + '[{"op": "select", + "table": "ordinals", + "where": [], + "sort": ["number"]}]']], + [[[{"uuid":["uuid","<0>"]}] +[{"uuid":["uuid","<1>"]}] +[{"count":0}] +[{"rows":[{"_uuid":["uuid","<0>"],"_version":["uuid","<2>"],"name":"zero","number":0},{"_uuid":["uuid","<1>"],"_version":["uuid","<3>"],"name":"one","number":1}]}] +]]) + +OVSDB_CHECK_EXECUTION([insert rows, delete all], + [ORDINAL_SCHEMA [\ + '[{"op": "insert", + "table": "ordinals", + "row": {"number": 0, "name": "zero"}, + "uuid-name": "first"}, + {"op": "insert", + "table": "ordinals", + "row": {"number": 1, "name": "one"}, + "uuid-name": "second"}, + {"op": "delete", + "table": "ordinals", + "where": []}, + {"op": "select", + "table": "ordinals", + "where": [], + "columns": ["name","number"]}]']], + [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"count":2},{"rows":[]}] +]]) + +OVSDB_CHECK_EXECUTION([insert row, query table, commit], + [ORDINAL_SCHEMA [\ + '[{"op": "insert", + "table": "ordinals", + "row": {"number": 0, "name": "zero"}}, + {"op": "select", + "table": "ordinals", + "where": []}, + {"op": "commit", + "durable": false}]']], + [[[{"uuid":["uuid","<0>"]},{"rows":[{"_uuid":["uuid","<0>"],"_version":["uuid","<1>"],"name":"zero","number":0}]},{}] +]]) + +OVSDB_CHECK_EXECUTION([insert row, query table, commit durably], + [ORDINAL_SCHEMA [\ + '[{"op": "insert", + "table": "ordinals", + "row": {"number": 0, "name": "zero"}}, + {"op": "select", + "table": "ordinals", + "where": []}, + {"op": "commit", + "durable": true}]']], + [[[{"uuid":["uuid","<0>"]},{"rows":[{"_uuid":["uuid","<0>"],"_version":["uuid","<1>"],"name":"zero","number":0}]},{}] +]]) + +OVSDB_CHECK_EXECUTION([equality wait with correct rows], + [ORDINAL_SCHEMA [\ + '[{"op": "insert", + "table": "ordinals", + "row": {"number": 0, "name": "zero"}}, + {"op": "insert", + "table": "ordinals", + "row": {"number": 1, "name": "one"}}, + {"op": "wait", + "timeout": 0, + "table": "ordinals", + "where": [], + "columns": ["name", "number"], + "until": "==", + "rows": [{"name": "zero", "number": 0}, + {"name": "one", "number": 1}]}]']], + [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{}] +]]) + +OVSDB_CHECK_EXECUTION([equality wait with extra row], + [ORDINAL_SCHEMA [\ + '[{"op": "insert", + "table": "ordinals", + "row": {"number": 0, "name": "zero"}}, + {"op": "insert", + "table": "ordinals", + "row": {"number": 1, "name": "one"}}, + {"op": "wait", + "timeout": 0, + "table": "ordinals", + "where": [], + "columns": ["name", "number"], + "until": "==", + "rows": [{"name": "zero", "number": 0}, + {"name": "one", "number": 1}, + {"name": "two", "number": 2}]}]']], + [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"details":"\"wait\" timed out","error":"timed out"}] +]]) + +OVSDB_CHECK_EXECUTION([equality wait with missing row], + [ORDINAL_SCHEMA [\ + '[{"op": "insert", + "table": "ordinals", + "row": {"number": 0, "name": "zero"}}, + {"op": "insert", + "table": "ordinals", + "row": {"number": 1, "name": "one"}}, + {"op": "wait", + "timeout": 0, + "table": "ordinals", + "where": [], + "columns": ["name", "number"], + "until": "==", + "rows": [{"name": "one", "number": 1}]}]']], + [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"details":"\"wait\" timed out","error":"timed out"}] +]]) + +OVSDB_CHECK_EXECUTION([inequality wait with correct rows], + [ORDINAL_SCHEMA [\ + '[{"op": "insert", + "table": "ordinals", + "row": {"number": 0, "name": "zero"}}, + {"op": "insert", + "table": "ordinals", + "row": {"number": 1, "name": "one"}}, + {"op": "wait", + "timeout": 0, + "table": "ordinals", + "where": [], + "columns": ["name", "number"], + "until": "!=", + "rows": [{"name": "zero", "number": 0}, + {"name": "one", "number": 1}]}]']], + [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"details":"\"wait\" timed out","error":"timed out"}] +]]) + +OVSDB_CHECK_EXECUTION([inequality wait with extra row], + [ORDINAL_SCHEMA [\ + '[{"op": "insert", + "table": "ordinals", + "row": {"number": 0, "name": "zero"}}, + {"op": "insert", + "table": "ordinals", + "row": {"number": 1, "name": "one"}}, + {"op": "wait", + "timeout": 0, + "table": "ordinals", + "where": [], + "columns": ["name", "number"], + "until": "!=", + "rows": [{"name": "zero", "number": 0}, + {"name": "one", "number": 1}, + {"name": "two", "number": 2}]}]']], + [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{}] +]]) + +OVSDB_CHECK_EXECUTION([inequality wait with missing row], + [ORDINAL_SCHEMA [\ + '[{"op": "insert", + "table": "ordinals", + "row": {"number": 0, "name": "zero"}}, + {"op": "insert", + "table": "ordinals", + "row": {"number": 1, "name": "one"}}, + {"op": "wait", + "timeout": 0, + "table": "ordinals", + "where": [], + "columns": ["name", "number"], + "until": "!=", + "rows": [{"name": "one", "number": 1}]}]']], + [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{}] +]]) diff --git a/tests/ovsdb-file.at b/tests/ovsdb-file.at new file mode 100644 index 00000000..c85b29e2 --- /dev/null +++ b/tests/ovsdb-file.at @@ -0,0 +1,282 @@ +AT_BANNER([OVSDB -- file I/O]) + +AT_SETUP([create empty, reread]) +AT_KEYWORDS([ovsdb file]) +AT_CAPTURE_FILE([file]) +OVS_CHECK_LCOV( + [test-ovsdb file-io file 'O_CREAT|O_RDWR'], [0], + [file: open successful +], [ignore]) +OVS_CHECK_LCOV( + [test-ovsdb file-io file 'O_RDONLY' read], [0], + [file: open successful +file: read: end of file +], [ignore]) +AT_CHECK([test -f .file.~lock~]) +AT_CLEANUP + +AT_SETUP([write one, reread]) +AT_KEYWORDS([ovsdb file]) +AT_CAPTURE_FILE([file]) +OVS_CHECK_LCOV( + [[test-ovsdb file-io file 'O_CREAT|O_RDWR' 'write:[0]']], [0], + [[file: open successful +file: write:[0] successful +]], [ignore]) +OVS_CHECK_LCOV( + [test-ovsdb file-io file 'O_RDONLY' read read], [0], + [[file: open successful +file: read: [0] +file: read: end of file +]], [ignore]) +AT_CHECK([test -f .file.~lock~]) +AT_CLEANUP + +AT_SETUP([check that O_EXCL works]) +AT_KEYWORDS([ovsdb file]) +AT_CAPTURE_FILE([file]) +OVS_CHECK_LCOV( + [[test-ovsdb file-io file 'O_CREAT|O_RDWR' 'write:[1]']], [0], + [[file: open successful +file: write:[1] successful +]], [ignore]) +OVS_CHECK_LCOV( + [test-ovsdb file-io file 'O_RDONLY' read], [0], + [[file: open successful +file: read: [1] +]], [ignore]) +OVS_CHECK_LCOV( + [test-ovsdb file-io file 'O_CREAT|O_RDWR|O_EXCL' read], [1], + [], [test-ovsdb: I/O error: create: file failed (File exists) +]) +AT_CHECK([test -f .file.~lock~]) +AT_CLEANUP + +AT_SETUP([write one, reread]) +AT_KEYWORDS([ovsdb file]) +AT_CAPTURE_FILE([file]) +OVS_CHECK_LCOV( + [[test-ovsdb file-io file 'O_CREAT|O_RDWR' 'write:[0]' 'write:[1]' 'write:[2]']], [0], + [[file: open successful +file: write:[0] successful +file: write:[1] successful +file: write:[2] successful +]], [ignore]) +OVS_CHECK_LCOV( + [test-ovsdb file-io file 'O_RDONLY' read read read read], [0], + [[file: open successful +file: read: [0] +file: read: [1] +file: read: [2] +file: read: end of file +]], [ignore]) +AT_CHECK([test -f .file.~lock~]) +AT_CLEANUP + +AT_SETUP([write one, reread, append]) +AT_KEYWORDS([ovsdb file]) +AT_CAPTURE_FILE([file]) +OVS_CHECK_LCOV( + [[test-ovsdb file-io file 'O_CREAT|O_RDWR' 'write:[0]' 'write:[1]' 'write:[2]']], [0], + [[file: open successful +file: write:[0] successful +file: write:[1] successful +file: write:[2] successful +]], [ignore]) +OVS_CHECK_LCOV( + [[test-ovsdb file-io file 'O_RDWR' read read read 'write:["append"]']], [0], + [[file: open successful +file: read: [0] +file: read: [1] +file: read: [2] +file: write:["append"] successful +]], [ignore]) +OVS_CHECK_LCOV( + [test-ovsdb file-io file 'O_RDONLY' read read read read read], [0], + [[file: open successful +file: read: [0] +file: read: [1] +file: read: [2] +file: read: ["append"] +file: read: end of file +]], [ignore]) +AT_CHECK([test -f .file.~lock~]) +AT_CLEANUP + +AT_SETUP([write, reread one, overwrite]) +AT_KEYWORDS([ovsdb file]) +AT_CAPTURE_FILE([file]) +OVS_CHECK_LCOV( + [[test-ovsdb file-io file 'O_CREAT|O_RDWR' 'write:[0]' 'write:[1]' 'write:[2]']], [0], + [[file: open successful +file: write:[0] successful +file: write:[1] successful +file: write:[2] successful +]], [ignore]) +OVS_CHECK_LCOV( + [[test-ovsdb file-io file 'O_RDWR' read 'write:["more data"]']], [0], + [[file: open successful +file: read: [0] +file: write:["more data"] successful +]], [ignore]) +OVS_CHECK_LCOV( + [test-ovsdb file-io file 'O_RDONLY' read read read], [0], + [[file: open successful +file: read: [0] +file: read: ["more data"] +file: read: end of file +]], [ignore]) +AT_CHECK([test -f .file.~lock~]) +AT_CLEANUP + +AT_SETUP([write, add corrupted data, read]) +AT_KEYWORDS([ovsdb file]) +AT_CAPTURE_FILE([file]) +OVS_CHECK_LCOV( + [[test-ovsdb file-io file 'O_CREAT|O_RDWR' 'write:[0]' 'write:[1]' 'write:[2]']], [0], + [[file: open successful +file: write:[0] successful +file: write:[1] successful +file: write:[2] successful +]], [ignore]) +AT_CHECK([echo 'xxx' >> file]) +OVS_CHECK_LCOV( + [test-ovsdb file-io file 'O_RDONLY' read read read read], [0], + [[file: open successful +file: read: [0] +file: read: [1] +file: read: [2] +file: read failed: syntax error: file: parse error at offset 174 in header line "xxx" +]], [ignore]) +AT_CHECK([test -f .file.~lock~]) +AT_CLEANUP + +AT_SETUP([write, add corrupted data, read, overwrite]) +AT_KEYWORDS([ovsdb file]) +AT_CAPTURE_FILE([file]) +OVS_CHECK_LCOV( + [[test-ovsdb file-io file 'O_CREAT|O_RDWR' 'write:[0]' 'write:[1]' 'write:[2]']], [0], + [[file: open successful +file: write:[0] successful +file: write:[1] successful +file: write:[2] successful +]], [ignore]) +AT_CHECK([echo 'xxx' >> file]) +OVS_CHECK_LCOV( + [[test-ovsdb file-io file 'O_RDWR' read read read read 'write:[3]']], [0], + [[file: open successful +file: read: [0] +file: read: [1] +file: read: [2] +file: read failed: syntax error: file: parse error at offset 174 in header line "xxx" +file: write:[3] successful +]], [ignore]) +OVS_CHECK_LCOV( + [test-ovsdb file-io file 'O_RDONLY' read read read read read], [0], + [[file: open successful +file: read: [0] +file: read: [1] +file: read: [2] +file: read: [3] +file: read: end of file +]], [ignore]) +AT_CHECK([test -f .file.~lock~]) +AT_CLEANUP + +AT_SETUP([write, corrupt some data, read, overwrite]) +AT_KEYWORDS([ovsdb file]) +AT_CAPTURE_FILE([file]) +OVS_CHECK_LCOV( + [[test-ovsdb file-io file 'O_CREAT|O_RDWR' 'write:[0]' 'write:[1]' 'write:[2]']], [0], + [[file: open successful +file: write:[0] successful +file: write:[1] successful +file: write:[2] successful +]], [ignore]) +AT_CHECK([[sed 's/\[2]/[3]/' < file > file.tmp]]) +AT_CHECK([mv file.tmp file]) +AT_CHECK([[grep -c '\[3]' file]], [0], [1 +]) +OVS_CHECK_LCOV( + [[test-ovsdb file-io file 'O_RDWR' read read read 'write:["longer data"]']], [0], + [[file: open successful +file: read: [0] +file: read: [1] +file: read failed: syntax error: file: 4 bytes starting at offset 170 have SHA-1 hash 5c031e5c0d3a9338cc127ebe40bb2748b6a67e78 but should have hash 98f55556e7ffd432381b56a19bd485b3e6446442 +file: write:["longer data"] successful +]], [ignore]) +OVS_CHECK_LCOV( + [test-ovsdb file-io file 'O_RDONLY' read read read read], [0], + [[file: open successful +file: read: [0] +file: read: [1] +file: read: ["longer data"] +file: read: end of file +]], [ignore]) +AT_CHECK([test -f .file.~lock~]) +AT_CLEANUP + +AT_SETUP([write, truncate file, read, overwrite]) +AT_KEYWORDS([ovsdb file]) +AT_CAPTURE_FILE([file]) +OVS_CHECK_LCOV( + [[test-ovsdb file-io file 'O_CREAT|O_RDWR' 'write:[0]' 'write:[1]' 'write:[2]']], [0], + [[file: open successful +file: write:[0] successful +file: write:[1] successful +file: write:[2] successful +]], [ignore]) +AT_CHECK([[sed 's/\[2]/2/' < file > file.tmp]]) +AT_CHECK([mv file.tmp file]) +AT_CHECK([[grep -c '^2$' file]], [0], [1 +]) +OVS_CHECK_LCOV( + [[test-ovsdb file-io file 'O_RDWR' read read read 'write:["longer data"]']], [0], + [[file: open successful +file: read: [0] +file: read: [1] +file: read failed: I/O error: file: error reading 4 bytes starting at offset 170 (unexpected end of file) +file: write:["longer data"] successful +]], [ignore]) +OVS_CHECK_LCOV( + [test-ovsdb file-io file 'O_RDONLY' read read read read], [0], + [[file: open successful +file: read: [0] +file: read: [1] +file: read: ["longer data"] +file: read: end of file +]], [ignore]) +AT_CHECK([test -f .file.~lock~]) +AT_CLEANUP + +AT_SETUP([write bad JSON, read, overwrite]) +AT_KEYWORDS([ovsdb file]) +AT_CAPTURE_FILE([file]) +OVS_CHECK_LCOV( + [[test-ovsdb file-io file 'O_CREAT|O_RDWR' 'write:[0]' 'write:[1]' 'write:[2]']], [0], + [[file: open successful +file: write:[0] successful +file: write:[1] successful +file: write:[2] successful +]], [ignore]) +AT_CHECK([[printf '%s\n%s\n' 'OVSDB JSON 5 d910b02871075d3156ec8675dfc95b7d5d640aa6' 'null' >> file]]) +OVS_CHECK_LCOV( + [[test-ovsdb file-io file 'O_RDWR' read read read read 'write:["replacement data"]']], [0], + [[file: open successful +file: read: [0] +file: read: [1] +file: read: [2] +file: read failed: syntax error: file: 5 bytes starting at offset 228 are not valid JSON (syntax error at beginning of input) +file: write:["replacement data"] successful +]], [ignore]) +OVS_CHECK_LCOV( + [test-ovsdb file-io file 'O_RDONLY' read read read read read], [0], + [[file: open successful +file: read: [0] +file: read: [1] +file: read: [2] +file: read: ["replacement data"] +file: read: end of file +]], [ignore]) +AT_CHECK([test -f .file.~lock~]) +AT_CLEANUP diff --git a/tests/ovsdb-query.at b/tests/ovsdb-query.at new file mode 100644 index 00000000..c39aa121 --- /dev/null +++ b/tests/ovsdb-query.at @@ -0,0 +1,535 @@ +AT_BANNER([OVSDB -- queries]) + +OVSDB_CHECK_POSITIVE([queries on scalars], + [[query \ + '{"columns": + {"i": {"type": "integer"}, + "r": {"type": "real"}, + "b": {"type": "boolean"}, + "s": {"type": "string"}, + "u": {"type": "uuid"}}}' \ + '[{"i": 0, + "r": 0.5, + "b": true, + "s": "a", + "u": ["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]}, + {"i": 1, + "r": 1.5, + "b": false, + "s": "b", + "u": ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]}, + {"i": 2, + "r": 2.5, + "b": true, + "s": "c", + "u": ["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]}, + {"i": 3, + "r": 3.5, + "b": false, + "s": "d", + "u": ["uuid", "62315898-64e0-40b9-b26f-ff74225303e6"]}, + {"i": 4, + "r": 4.5, + "b": true, + "s": "e", + "u": ["uuid", "4a5127e2-0256-4a72-a7dc-6246213967c7"]}]' \ + '[[], + [["i", "==", 0]], + [["i", "!=", 1]], + [["i", "<", 2]], + [["i", "<=", 3]], + [["i", ">", 2]], + [["i", ">=", 4]], + [["i", "includes", 3]], + [["i", "excludes", 2]], + [["r", "==", 0.5]], + [["r", "!=", 1.5]], + [["r", "<", 2.5]], + [["r", "<=", 3.5]], + [["r", ">", 4.5]], + [["r", ">=", 5.5]], + [["r", "includes", 1]], + [["r", "excludes", 3]], + [["b", "==", true]], + [["b", "!=", true]], + [["b", "includes", false]], + [["b", "excludes", true]], + [["s", "==", "a"]], + [["s", "!=", "b"]], + [["s", "includes", "c"]], + [["s", "excludes", "d"]], + [["u", "==", ["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]]], + [["u", "!=", ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]], + [["u", "includes",["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]]]]']], + [dnl +query 0: 11111 +query 1: 1---- +query 2: 1-111 +query 3: 11--- +query 4: 1111- +query 5: ---11 +query 6: ----1 +query 7: ---1- +query 8: 11-11 +query 9: 1---- +query 10: 1-111 +query 11: 11--- +query 12: 1111- +query 13: ----- +query 14: ----- +query 15: ----- +query 16: 11111 +query 17: 1-1-1 +query 18: -1-1- +query 19: -1-1- +query 20: -1-1- +query 21: 1---- +query 22: 1-111 +query 23: --1-- +query 24: 111-1 +query 25: 1---- +query 26: 1-111 +query 27: --1--], + [query]) + +OVSDB_CHECK_POSITIVE([queries on sets], + [[query \ + '{"columns": {"i": {"type": {"key": "integer", "min": 0, "max": "unlimited"}}}}' \ + '[{"i": ["set", []]}, + {"i": ["set", [0]]}, + {"i": ["set", [1]]}, + {"i": ["set", [0, 1]]}, + {"i": ["set", [2]]}, + {"i": ["set", [2, 0]]}, + {"i": ["set", [2, 1]]}, + {"i": ["set", [2, 1, 0]]}]' \ + '[[], + [["i", "==", ["set", []]]], + [["i", "==", ["set", [0]]]], + [["i", "==", ["set", [1]]]], + [["i", "==", ["set", [0, 1]]]], + [["i", "==", ["set", [2]]]], + [["i", "==", ["set", [2, 0]]]], + [["i", "==", ["set", [2, 1]]]], + [["i", "==", ["set", [2, 1, 0]]]], + [["i", "!=", ["set", []]]], + [["i", "!=", ["set", [0]]]], + [["i", "!=", ["set", [1]]]], + [["i", "!=", ["set", [0, 1]]]], + [["i", "!=", ["set", [2]]]], + [["i", "!=", ["set", [2, 0]]]], + [["i", "!=", ["set", [2, 1]]]], + [["i", "!=", ["set", [2, 1, 0]]]], + [["i", "includes", ["set", []]]], + [["i", "includes", ["set", [0]]]], + [["i", "includes", ["set", [1]]]], + [["i", "includes", ["set", [0, 1]]]], + [["i", "includes", ["set", [2]]]], + [["i", "includes", ["set", [2, 0]]]], + [["i", "includes", ["set", [2, 1]]]], + [["i", "includes", ["set", [2, 1, 0]]]], + [["i", "excludes", ["set", []]]], + [["i", "excludes", ["set", [0]]]], + [["i", "excludes", ["set", [1]]]], + [["i", "excludes", ["set", [0, 1]]]], + [["i", "excludes", ["set", [2]]]], + [["i", "excludes", ["set", [2, 0]]]], + [["i", "excludes", ["set", [2, 1]]]], + [["i", "excludes", ["set", [2, 1, 0]]]]]']], + [dnl +query 0: 11111 111 +query 1: 1---- --- +query 2: -1--- --- +query 3: --1-- --- +query 4: ---1- --- +query 5: ----1 --- +query 6: ----- 1-- +query 7: ----- -1- +query 8: ----- --1 +query 9: -1111 111 +query 10: 1-111 111 +query 11: 11-11 111 +query 12: 111-1 111 +query 13: 1111- 111 +query 14: 11111 -11 +query 15: 11111 1-1 +query 16: 11111 11- +query 17: 11111 111 +query 18: -1-1- 1-1 +query 19: --11- -11 +query 20: ---1- --1 +query 21: ----1 111 +query 22: ----- 1-1 +query 23: ----- -11 +query 24: ----- --1 +query 25: 11111 111 +query 26: 1-1-1 -1- +query 27: 11--1 1-- +query 28: 1---1 --- +query 29: 1111- --- +query 30: 1-1-- --- +query 31: 11--- --- +query 32: 1---- ---], [query]) + +# This is the same as the "set" test except that it adds values, +# all of which always match. +OVSDB_CHECK_POSITIVE([queries on maps (1)], + [[query \ + '{"columns": {"i": {"type": {"key": "integer", + "value": "boolean", + "min": 0, + "max": "unlimited"}}}}' \ + '[{"i": ["map", []]}, + {"i": ["map", [[0, true]]]}, + {"i": ["map", [[1, false]]]}, + {"i": ["map", [[0, true], [1, false]]]}, + {"i": ["map", [[2, true]]]}, + {"i": ["map", [[2, true], [0, true]]]}, + {"i": ["map", [[2, true], [1, false]]]}, + {"i": ["map", [[2, true], [1, false], [0, true]]]}]' \ + '[[], + [["i", "==", ["map", []]]], + [["i", "==", ["map", [[0, true]]]]], + [["i", "==", ["map", [[1, false]]]]], + [["i", "==", ["map", [[0, true], [1, false]]]]], + [["i", "==", ["map", [[2, true]]]]], + [["i", "==", ["map", [[2, true], [0, true]]]]], + [["i", "==", ["map", [[2, true], [1, false]]]]], + [["i", "==", ["map", [[2, true], [1, false], [0, true]]]]], + [["i", "!=", ["map", []]]], + [["i", "!=", ["map", [[0, true]]]]], + [["i", "!=", ["map", [[1, false]]]]], + [["i", "!=", ["map", [[0, true], [1, false]]]]], + [["i", "!=", ["map", [[2, true]]]]], + [["i", "!=", ["map", [[2, true], [0, true]]]]], + [["i", "!=", ["map", [[2, true], [1, false]]]]], + [["i", "!=", ["map", [[2, true], [1, false], [0, true]]]]], + [["i", "includes", ["map", []]]], + [["i", "includes", ["map", [[0, true]]]]], + [["i", "includes", ["map", [[1, false]]]]], + [["i", "includes", ["map", [[0, true], [1, false]]]]], + [["i", "includes", ["map", [[2, true]]]]], + [["i", "includes", ["map", [[2, true], [0, true]]]]], + [["i", "includes", ["map", [[2, true], [1, false]]]]], + [["i", "includes", ["map", [[2, true], [1, false], [0, true]]]]], + [["i", "excludes", ["map", []]]], + [["i", "excludes", ["map", [[0, true]]]]], + [["i", "excludes", ["map", [[1, false]]]]], + [["i", "excludes", ["map", [[0, true], [1, false]]]]], + [["i", "excludes", ["map", [[2, true]]]]], + [["i", "excludes", ["map", [[2, true], [0, true]]]]], + [["i", "excludes", ["map", [[2, true], [1, false]]]]], + [["i", "excludes", ["map", [[2, true], [1, false], [0, true]]]]]]']], + [dnl +query 0: 11111 111 +query 1: 1---- --- +query 2: -1--- --- +query 3: --1-- --- +query 4: ---1- --- +query 5: ----1 --- +query 6: ----- 1-- +query 7: ----- -1- +query 8: ----- --1 +query 9: -1111 111 +query 10: 1-111 111 +query 11: 11-11 111 +query 12: 111-1 111 +query 13: 1111- 111 +query 14: 11111 -11 +query 15: 11111 1-1 +query 16: 11111 11- +query 17: 11111 111 +query 18: -1-1- 1-1 +query 19: --11- -11 +query 20: ---1- --1 +query 21: ----1 111 +query 22: ----- 1-1 +query 23: ----- -11 +query 24: ----- --1 +query 25: 11111 111 +query 26: 1-1-1 -1- +query 27: 11--1 1-- +query 28: 1---1 --- +query 29: 1111- --- +query 30: 1-1-- --- +query 31: 11--- --- +query 32: 1---- ---], [query]) + +# This is the same as the "set" test except that it adds values, +# and those values don't always match. +OVSDB_CHECK_POSITIVE([queries on maps (2)], + [[query \ + '{"columns": {"i": {"type": {"key": "integer", + "value": "boolean", + "min": 0, + "max": "unlimited"}}}}' \ + '[{"i": ["map", []]}, + {"i": ["map", [[0, true]]]}, + {"i": ["map", [[0, false]]]}, + {"i": ["map", [[1, false]]]}, + {"i": ["map", [[1, true]]]}, + + {"i": ["map", [[0, true], [1, false]]]}, + {"i": ["map", [[0, true], [1, true]]]}, + {"i": ["map", [[2, true]]]}, + {"i": ["map", [[2, false]]]}, + {"i": ["map", [[2, true], [0, true]]]}, + + {"i": ["map", [[2, false], [0, true]]]}, + {"i": ["map", [[2, true], [1, false]]]}, + {"i": ["map", [[2, true], [1, true]]]}, + {"i": ["map", [[2, true], [1, false], [0, true]]]}, + {"i": ["map", [[2, true], [1, false], [0, false]]]}]' \ + '[[], + [["i", "==", ["map", []]]], + [["i", "==", ["map", [[0, true]]]]], + [["i", "==", ["map", [[1, false]]]]], + [["i", "==", ["map", [[0, true], [1, false]]]]], + [["i", "==", ["map", [[2, true]]]]], + [["i", "==", ["map", [[2, true], [0, true]]]]], + [["i", "==", ["map", [[2, true], [1, false]]]]], + [["i", "==", ["map", [[2, true], [1, false], [0, true]]]]], + [["i", "!=", ["map", []]]], + [["i", "!=", ["map", [[0, true]]]]], + [["i", "!=", ["map", [[1, false]]]]], + [["i", "!=", ["map", [[0, true], [1, false]]]]], + [["i", "!=", ["map", [[2, true]]]]], + [["i", "!=", ["map", [[2, true], [0, true]]]]], + [["i", "!=", ["map", [[2, true], [1, false]]]]], + [["i", "!=", ["map", [[2, true], [1, false], [0, true]]]]], + [["i", "includes", ["map", []]]], + [["i", "includes", ["map", [[0, true]]]]], + [["i", "includes", ["map", [[1, false]]]]], + [["i", "includes", ["map", [[0, true], [1, false]]]]], + [["i", "includes", ["map", [[2, true]]]]], + [["i", "includes", ["map", [[2, true], [0, true]]]]], + [["i", "includes", ["map", [[2, true], [1, false]]]]], + [["i", "includes", ["map", [[2, true], [1, false], [0, true]]]]], + [["i", "excludes", ["map", []]]], + [["i", "excludes", ["map", [[0, true]]]]], + [["i", "excludes", ["map", [[1, false]]]]], + [["i", "excludes", ["map", [[0, true], [1, false]]]]], + [["i", "excludes", ["map", [[2, true]]]]], + [["i", "excludes", ["map", [[2, true], [0, true]]]]], + [["i", "excludes", ["map", [[2, true], [1, false]]]]], + [["i", "excludes", ["map", [[2, true], [1, false], [0, true]]]]]]']], + [dnl +query 0: 11111 11111 11111 +query 1: 1---- ----- ----- +query 2: -1--- ----- ----- +query 3: ---1- ----- ----- +query 4: ----- 1---- ----- +query 5: ----- --1-- ----- +query 6: ----- ----1 ----- +query 7: ----- ----- -1--- +query 8: ----- ----- ---1- +query 9: -1111 11111 11111 +query 10: 1-111 11111 11111 +query 11: 111-1 11111 11111 +query 12: 11111 -1111 11111 +query 13: 11111 11-11 11111 +query 14: 11111 1111- 11111 +query 15: 11111 11111 1-111 +query 16: 11111 11111 111-1 +query 17: 11111 11111 11111 +query 18: -1--- 11--1 1--1- +query 19: ---1- 1---- -1-11 +query 20: ----- 1---- ---1- +query 21: ----- --1-1 -1111 +query 22: ----- ----1 ---1- +query 23: ----- ----- -1-11 +query 24: ----- ----- ---1- +query 25: 11111 11111 11111 +query 26: 1-111 --11- -11-1 +query 27: 111-1 -1111 1-1-- +query 28: 1-1-1 --11- --1-- +query 29: 11111 11-1- 1---- +query 30: 1-111 ---1- ----- +query 31: 111-1 -1-1- 1---- +query 32: 1-1-1 ---1- -----], [query]) + +OVSDB_CHECK_POSITIVE([UUID-distinct queries on scalars], + [[query-distinct \ + '{"columns": + {"i": {"type": "integer"}, + "r": {"type": "real"}, + "b": {"type": "boolean"}, + "s": {"type": "string"}, + "u": {"type": "uuid"}}}' \ + '[{"i": 0, + "r": 0.5, + "b": true, + "s": "a", + "u": ["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]}, + {"i": 1, + "r": 1.5, + "b": false, + "s": "b", + "u": ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]}, + {"i": 2, + "r": 2.5, + "b": true, + "s": "c", + "u": ["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]}, + {"i": 3, + "r": 3.5, + "b": false, + "s": "d", + "u": ["uuid", "62315898-64e0-40b9-b26f-ff74225303e6"]}, + {"i": 4, + "r": 4.5, + "b": true, + "s": "e", + "u": ["uuid", "4a5127e2-0256-4a72-a7dc-6246213967c7"]}]' \ + '[[], + [["i", "==", 0]], + [["i", "!=", 1]], + [["i", "<", 2]], + [["i", "<=", 3]], + [["i", ">", 2]], + [["i", ">=", 4]], + [["i", "includes", 3]], + [["i", "excludes", 2]], + [["r", "==", 0.5]], + [["r", "!=", 1.5]], + [["r", "<", 2.5]], + [["r", "<=", 3.5]], + [["r", ">", 4.5]], + [["r", ">=", 5.5]], + [["r", "includes", 1]], + [["r", "excludes", 3]], + [["b", "==", true]], + [["b", "!=", true]], + [["b", "includes", false]], + [["b", "excludes", true]], + [["s", "==", "a"]], + [["s", "!=", "b"]], + [["s", "includes", "c"]], + [["s", "excludes", "d"]], + [["u", "==", ["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]]], + [["u", "!=", ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]], + [["u", "includes",["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]]]]' \ + '["_uuid"]']], + [dnl +query 0: abcde +query 1: a---- +query 2: a-cde +query 3: ab--- +query 4: abcd- +query 5: ---de +query 6: ----e +query 7: ---d- +query 8: ab-de +query 9: a---- +query 10: a-cde +query 11: ab--- +query 12: abcd- +query 13: ----- +query 14: ----- +query 15: ----- +query 16: abcde +query 17: a-c-e +query 18: -b-d- +query 19: -b-d- +query 20: -b-d- +query 21: a---- +query 22: a-cde +query 23: --c-- +query 24: abc-e +query 25: a---- +query 26: a-cde +query 27: --c--], + [query]) + +OVSDB_CHECK_POSITIVE([Boolean-distinct queries on scalars], + [[query-distinct \ + '{"columns": + {"i": {"type": "integer"}, + "r": {"type": "real"}, + "b": {"type": "boolean"}, + "s": {"type": "string"}, + "u": {"type": "uuid"}}}' \ + '[{"i": 0, + "r": 0.5, + "b": true, + "s": "a", + "u": ["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]}, + {"i": 1, + "r": 1.5, + "b": false, + "s": "b", + "u": ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]}, + {"i": 2, + "r": 2.5, + "b": true, + "s": "c", + "u": ["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]}, + {"i": 3, + "r": 3.5, + "b": false, + "s": "d", + "u": ["uuid", "62315898-64e0-40b9-b26f-ff74225303e6"]}, + {"i": 4, + "r": 4.5, + "b": true, + "s": "e", + "u": ["uuid", "4a5127e2-0256-4a72-a7dc-6246213967c7"]}]' \ + '[[], + [["i", "==", 0]], + [["i", "!=", 1]], + [["i", "<", 2]], + [["i", "<=", 3]], + [["i", ">", 2]], + [["i", ">=", 4]], + [["i", "includes", 3]], + [["i", "excludes", 2]], + [["r", "==", 0.5]], + [["r", "!=", 1.5]], + [["r", "<", 2.5]], + [["r", "<=", 3.5]], + [["r", ">", 4.5]], + [["r", ">=", 5.5]], + [["r", "includes", 1]], + [["r", "excludes", 3]], + [["b", "==", true]], + [["b", "!=", true]], + [["b", "includes", false]], + [["b", "excludes", true]], + [["s", "==", "a"]], + [["s", "!=", "b"]], + [["s", "includes", "c"]], + [["s", "excludes", "d"]], + [["u", "==", ["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]]], + [["u", "!=", ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]], + [["u", "includes",["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]]]]' \ + '["b"]']], + [dnl +query 0: ababa +query 1: a-a-a +query 2: ababa +query 3: ababa +query 4: ababa +query 5: ababa +query 6: a-a-a +query 7: -b-b- +query 8: ababa +query 9: a-a-a +query 10: ababa +query 11: ababa +query 12: ababa +query 13: ----- +query 14: ----- +query 15: ----- +query 16: ababa +query 17: a-a-a +query 18: -b-b- +query 19: -b-b- +query 20: -b-b- +query 21: a-a-a +query 22: ababa +query 23: a-a-a +query 24: ababa +query 25: a-a-a +query 26: ababa +query 27: a-a-a], + [query]) diff --git a/tests/ovsdb-row.at b/tests/ovsdb-row.at new file mode 100644 index 00000000..e631f6f8 --- /dev/null +++ b/tests/ovsdb-row.at @@ -0,0 +1,277 @@ +AT_BANNER([OVSDB -- rows]) + +# Autoconf 2.63 has a bug that causes the double-quotes below to be +# lost, so that the following tests fail, so we mark them as XFAIL for +# Autoconf < 2.64. + +m4_define([RESERVED_COLUMNS], [["_uuid":["uuid","00000000-0000-0000-0000-000000000000"],"_version":["uuid","00000000-0000-0000-0000-000000000000"]]]) + +OVSDB_CHECK_POSITIVE([row with one string column], + [[parse-rows \ + '{"columns": {"name": {"type": "string"}}}' \ + '{"name": "value"}' \ + '{"name": ""}' \ + '{"name": "longer string with spaces"}' \ + '{}']], + [{RESERVED_COLUMNS,"name":"value"} +name +{RESERVED_COLUMNS,"name":""} +name +{RESERVED_COLUMNS,"name":"longer string with spaces"} +name +{RESERVED_COLUMNS,"name":""} +], [], [2.64]) + +OVSDB_CHECK_POSITIVE([row with one integer column], + [[parse-rows \ + '{"columns": {"count": {"type": "integer"}}}' \ + '{"count": 1}' \ + '{"count": -1}' \ + '{"count": 2e10}' \ + '{}']], + [{RESERVED_COLUMNS,"count":1} +count +{RESERVED_COLUMNS,"count":-1} +count +{RESERVED_COLUMNS,"count":20000000000} +count +{RESERVED_COLUMNS,"count":0} +], [], [2.64]) + +OVSDB_CHECK_POSITIVE([row with one real column], + [[parse-rows \ + '{"columns": {"cost": {"type": "real"}}}' \ + '{"cost": 1.0}' \ + '{"cost": -2.0}' \ + '{"cost": 123000}' \ + '{}']], + [{RESERVED_COLUMNS,"cost":1} +cost +{RESERVED_COLUMNS,"cost":-2} +cost +{RESERVED_COLUMNS,"cost":123000} +cost +{RESERVED_COLUMNS,"cost":0} +], [], [2.64]) + +OVSDB_CHECK_POSITIVE([row with one boolean column], + [[parse-rows \ + '{"columns": {"feasible": {"type": "boolean"}}}' \ + '{"feasible": true}' \ + '{"feasible": false}' \ + '{}']], + [{RESERVED_COLUMNS,"feasible":true} +feasible +{RESERVED_COLUMNS,"feasible":false} +feasible +{RESERVED_COLUMNS,"feasible":false} +], [], [2.64]) + +OVSDB_CHECK_POSITIVE([row with one uuid column], + [[parse-rows \ + '{"columns": {"ref": {"type": "uuid"}}}' \ + '{"ref": ["uuid", "f707423d-bf5b-48b5-b6c0-797c900ba4b6"]}' \ + '{"ref": ["uuid", "33583cc5-d2f4-43de-b1ca-8aac14071b51"]}' \ + '{}']], + [{RESERVED_COLUMNS,"ref":[["uuid","f707423d-bf5b-48b5-b6c0-797c900ba4b6"]]} +ref +{RESERVED_COLUMNS,"ref":[["uuid","33583cc5-d2f4-43de-b1ca-8aac14071b51"]]} +ref +{RESERVED_COLUMNS,"ref":[["uuid","00000000-0000-0000-0000-000000000000"]]} +], [], [2.64]) + +OVSDB_CHECK_POSITIVE([row with set of 1 to 2 elements], + [[parse-rows \ + '{"columns": {"myset": {"type": {"key": "integer", "min": 1, "max": 2}}}}' \ + '{}']], + [{RESERVED_COLUMNS,["myset":["set",[0]]]} +]) + +OVSDB_CHECK_POSITIVE([row with map of 1 to 2 elements], + [[parse-rows \ + '{"columns": {"mymap": {"type": {"key": "integer", "value": "uuid", "min": 1, "max": 2}}}}' \ + '{}']], + [{RESERVED_COLUMNS,["mymap":["map",[[0,["uuid","00000000-0000-0000-0000-000000000000"]]]]]} +], [], [2.64]) + +OVSDB_CHECK_POSITIVE([row with several columns], + [[parse-rows \ + '{"columns": + {"vswitch": {"type": "uuid"}, + "name": {"type": "string"}, + "datapath_id": {"type": {"key": "string", "min": 0}}, + "hwaddr": {"type": "string"}, + "mirrors": {"type": {"key": "uuid", "min": 0, "max": "unlimited"}}, + "netflows": {"type": {"key": "uuid", "min": 0, "max": "unlimited"}}, + "controller": {"type": {"key": "uuid", "min": 0}}, + "listeners": {"type": {"key": "uuid", "min": 0, "max": "unlimited"}}, + "snoops": {"type": {"key": "uuid", "min": 0, "max": "unlimited"}}}}' \ + '{"vswitch": ["uuid", "1a5c7280-0d4c-4e34-9ec7-c772339f7774"], + "name": "br0", + "datapath_id": ["set", ["000ae4256bb0"]], + "hwaddr": "00:0a:e4:25:6b:b0"}' \ + '{}']], + [{RESERVED_COLUMNS,["controller":["set",[]],"datapath_id":["set",["000ae4256bb0"]],"hwaddr":"00:0a:e4:25:6b:b0","listeners":["set",[]],"mirrors":["set",[]],"name":"br0","netflows":["set",[]],"snoops":["set",[]],"vswitch":["uuid","1a5c7280-0d4c-4e34-9ec7-c772339f7774"]]} +datapath_id, hwaddr, name, vswitch +{RESERVED_COLUMNS,["controller":["set",[]],"datapath_id":["set",[]],"hwaddr":"","listeners":["set",[]],"mirrors":["set",[]],"name":"","netflows":["set",[]],"snoops":["set",[]],"vswitch":["uuid","00000000-0000-0000-0000-000000000000"]]} +], [], [2.64]) + +OVSDB_CHECK_POSITIVE([row hashing (scalars)], + [[compare-rows \ + '{"columns": + {"i": {"type": "integer"}, + "r": {"type": "real"}, + "b": {"type": "boolean"}, + "s": {"type": "string"}, + "u": {"type": "uuid"}}}' \ + '["null", {}]' \ + '["i1", {"i": 1}]' \ + '["i2", {"i": 2}]' \ + '["i4", {"i": 4}]' \ + '["i8", {"i": 8}]' \ + '["i16", {"i": 16}]' \ + '["i32", {"i": 32}]' \ + '["i64", {"i": 64}]' \ + '["i128", {"i": 128}]' \ + '["i256", {"i": 256}]' \ + '["null2", {"r": -0}]' \ + '["r123", {"r": 123}]' \ + '["r0.0625", {"r": 0.0625}]' \ + '["r0.125", {"r": 0.125}]' \ + '["r0.25", {"r": 0.25}]' \ + '["r0.5", {"r": 0.5}]' \ + '["r1", {"r": 1}]' \ + '["r2", {"r": 2}]' \ + '["r4", {"r": 4}]' \ + '["r8", {"r": 8}]' \ + '["r16", {"r": 16}]' \ + '["r32", {"r": 32}]' \ + '["null3", {"b": false}]' \ + '["b1", {"b": true}]' \ + '["null4", {"s": ""}]' \ + '["s0", {"s": "a"}]' \ + '["s1", {"s": "b"}]' \ + '["s2", {"s": "c"}]' \ + '["s3", {"s": "d"}]' \ + '["s4", {"s": "e"}]' \ + '["s5", {"s": "f"}]' \ + '["s6", {"s": "g"}]' \ + '["s7", {"s": "h"}]' \ + '["s8", {"s": "i"}]' \ + '["s9", {"s": "j"}]' \ + '["null5", {"u": ["uuid","00000000-0000-0000-0000-000000000000"]}]' \ + '["u1", {"u": ["uuid","10000000-0000-0000-0000-000000000000"]}]' \ + '["u2", {"u": ["uuid","01000000-0000-0000-0000-000000000000"]}]' \ + '["u3", {"u": ["uuid","00100000-0000-0000-0000-000000000000"]}]' \ + '["u4", {"u": ["uuid","00010000-0000-0000-0000-000000000000"]}]' \ + '["u5", {"u": ["uuid","00001000-0000-0000-0000-000000000000"]}]' \ + '["u6", {"u": ["uuid","00000100-0000-0000-0000-000000000000"]}]' \ + '["u7", {"u": ["uuid","00000010-0000-0000-0000-000000000000"]}]' \ + '["u8", {"u": ["uuid","00000001-0000-0000-0000-000000000000"]}]' \ + '["null6", {"u": ["uuid","00000000-c6db-4d22-970f-b41fabd20c4b"]}]']], + [[null == null2 +null == null3 +null == null4 +null == null5 +hash(null) == hash(null6) +null2 == null3 +null2 == null4 +null2 == null5 +hash(null2) == hash(null6) +null3 == null4 +null3 == null5 +hash(null3) == hash(null6) +null4 == null5 +hash(null4) == hash(null6) +hash(null5) == hash(null6)]]) + +OVSDB_CHECK_POSITIVE([row hashing (sets)], + [[compare-rows \ + '{"columns": + {"i": {"type": {"key": "integer", "min": 0, "max": "unlimited"}}, + "r": {"type": {"key": "real", "min": 0, "max": "unlimited"}}, + "b": {"type": {"key": "boolean", "min": 0, "max": "unlimited"}}, + "s": {"type": {"key": "string", "min": 0, "max": "unlimited"}}, + "u": {"type": {"key": "uuid", "min": 0, "max": "unlimited"}}}}' \ + '["null0", {"i": ["set", []]}]' \ + '["i0", {"i": ["set", [0]]}]' \ + '["i01", {"i": ["set", [0, 1]]}]' \ + '["i012", {"i": ["set", [0, 1, 2]]}]' \ + '["i021", {"i": ["set", [0, 2, 1]]}]' \ + '["i201", {"i": ["set", [2, 0, 1]]}]' \ + '["i102", {"i": ["set", [1, 0, 2]]}]' \ + '["i120", {"i": ["set", [1, 2, 0]]}]' \ + '["i210", {"i": ["set", [2, 1, 0]]}]' \ + '["r0", {"r": ["set", [0]]}]' \ + '["r01", {"r": ["set", [0, 1]]}]' \ + '["r012", {"r": ["set", [0, 1, 2]]}]' \ + '["r201", {"r": ["set", [2, 0, 1]]}]' \ + '["null1", {"b": ["set", []]}]' \ + '["b0", {"b": ["set", [false]]}]' \ + '["b1", {"b": ["set", [true]]}]' \ + '["b01", {"b": ["set", [false, true]]}]' \ + '["b10", {"b": ["set", [true, false]]}]' \ + '["null2", {"s": ["set", []]}]' \ + '["sa", {"s": ["set", ["a"]]}]' \ + '["sb", {"s": ["set", ["b"]]}]' \ + '["sab", {"s": ["set", ["a", "b"]]}]' \ + '["sba", {"s": ["set", ["b", "a"]]}]']], + [[null0 == null1 +null0 == null2 +i012 == i021 +i012 == i201 +i012 == i102 +i012 == i120 +i012 == i210 +i021 == i201 +i021 == i102 +i021 == i120 +i021 == i210 +i201 == i102 +i201 == i120 +i201 == i210 +i102 == i120 +i102 == i210 +i120 == i210 +r012 == r201 +null1 == null2 +b01 == b10 +sab == sba]]) + +OVSDB_CHECK_POSITIVE([row hashing (maps)], + [[compare-rows \ + '{"columns": + {"ii": {"type": {"key": "integer", "value": "integer", + "min": 0, "max": "unlimited"}}, + "rr": {"type": {"key": "real", "value": "real", + "min": 0, "max": "unlimited"}}, + "bb": {"type": {"key": "boolean", "value": "boolean", + "min": 0, "max": "unlimited"}}, + "ss": {"type": {"key": "string", "value": "string", + "min": 0, "max": "unlimited"}}}}' \ + '["null", {}]' \ + '["ii0", {"ii": ["map", [[0, 0]]]}]' \ + '["ii1", {"ii": ["map", [[0, 1]]]}]' \ + '["ii00", {"ii": ["map", [[0, 0], [1, 0]]]}]' \ + '["ii01", {"ii": ["map", [[0, 0], [1, 1]]]}]' \ + '["ii10", {"ii": ["map", [[0, 1], [1, 0]]]}]' \ + '["ii11", {"ii": ["map", [[0, 1], [1, 1]]]}]' \ + '["rr0", {"rr": ["map", [[0, 0]]]}]' \ + '["rr0", {"rr": ["map", [[0, 1]]]}]' \ + '["rr00", {"rr": ["map", [[0, 0], [1, 0]]]}]' \ + '["rr01", {"rr": ["map", [[0, 0], [1, 1]]]}]' \ + '["rr10", {"rr": ["map", [[0, 1], [1, 0]]]}]' \ + '["rr11", {"rr": ["map", [[0, 1], [1, 1]]]}]' \ + '["bb0", {"bb": ["map", [[false, false]]]}]' \ + '["bb1", {"bb": ["map", [[false, true]]]}]' \ + '["bb00", {"bb": ["map", [[false, false], [true, false]]]}]' \ + '["bb01", {"bb": ["map", [[false, false], [true, true]]]}]' \ + '["bb10", {"bb": ["map", [[false, true], [true, false]]]}]' \ + '["bb11", {"bb": ["map", [[false, true], [true, true]]]}]' \ + '["ss0", {"ss": ["map", [["a", "a"]]]}]' \ + '["ss1", {"ss": ["map", [["a", "b"]]]}]' \ + '["ss00", {"ss": ["map", [["a", "a"], ["b", "a"]]]}]' \ + '["ss01", {"ss": ["map", [["a", "a"], ["b", "b"]]]}]' \ + '["ss10", {"ss": ["map", [["a", "b"], ["b", "a"]]]}]' \ + '["ss11", {"ss": ["map", [["a", "b"], ["b", "b"]]]}]'; echo +]], [[]]) diff --git a/tests/ovsdb-table.at b/tests/ovsdb-table.at new file mode 100644 index 00000000..ebc5992d --- /dev/null +++ b/tests/ovsdb-table.at @@ -0,0 +1,31 @@ +AT_BANNER([OVSDB -- tables]) + +OVSDB_CHECK_POSITIVE([table with one column], + [[parse-table mytable '{"columns": {"name": {"type": "string"}}}']], + [[{"columns":{"name":{"type":"string"}}}]]) + +OVSDB_CHECK_POSITIVE([immutable table with one column], + [[parse-table mytable \ + '{"columns": {"name": {"type": "string"}}, + "mutable": false}']], + [[{"columns":{"name":{"type":"string"}},"mutable":false}]]) + +OVSDB_CHECK_POSITIVE([table with comment], + [[parse-table mytable \ + '{"columns": {"name": {"type": "string"}}, + "comment": "description of table"}']], + [[{"columns":{"name":{"type":"string"}},"comment":"description of table"}]]) + +OVSDB_CHECK_NEGATIVE([column names may not begin with _], + [[parse-table mytable \ + '{"columns": {"_column": {"type": "integer"}}}']], + [[names beginning with "_" are reserved]], + [table]) + +OVSDB_CHECK_NEGATIVE([table must have at least one column (1)], + [[parse-table mytable '{}']], + [[Parsing table schema for table mytable failed: Required 'columns' member is missing.]]) + +OVSDB_CHECK_NEGATIVE([table must have at least one column (2)], + [[parse-table mytable '{"columns": {}}']], + [[table must have at least one column]]) diff --git a/tests/ovsdb-transaction.at b/tests/ovsdb-transaction.at new file mode 100644 index 00000000..f0c29d43 --- /dev/null +++ b/tests/ovsdb-transaction.at @@ -0,0 +1,384 @@ +AT_BANNER([OVSDB -- transactions]) + +OVSDB_CHECK_POSITIVE([empty table, empty transaction], + [[transact \ + '["print"]' \ + '["commit"]' \ + '["print"]' \ + '["abort"]' \ + '["print"]']], + [dnl +print: +commit: +print: +abort: +print:]) + +OVSDB_CHECK_POSITIVE([nonempty table, empty transaction], + [[transact \ + '["insert", "1", "2", "3"]' \ + '["insert", "2", "2", "3"]' \ + '["print"]' \ + '["commit"]' \ + '["print"]' \ + '["abort"]' \ + '["print"]']], + [dnl +insert 1 2 3: +insert 2 2 3: +print: +1: i=2, j=3 +2: i=2, j=3 +commit: +print: +1: i=2, j=3 +2: i=2, j=3 +abort: +print: +1: i=2, j=3 +2: i=2, j=3]) + +OVSDB_CHECK_POSITIVE([insert, commit], + [[transact \ + '["insert", "1", "2", "3"]' \ + '["insert", "2", "2", "3"]' \ + '["commit"]' \ + '["print"]' \ + '["insert", "3", "1", "2"]' \ + '["print"]' \ + '["commit"]' \ + '["print"]']], + [dnl +insert 1 2 3: +insert 2 2 3: +commit: +print: +1: i=2, j=3 +2: i=2, j=3 +insert 3 1 2: +print: +1: i=2, j=3 +2: i=2, j=3 +3: i=1, j=2 +commit: +print: +1: i=2, j=3 +2: i=2, j=3 +3: i=1, j=2], + [transaction]) + +OVSDB_CHECK_POSITIVE([insert, abort], + [[transact \ + '["insert", "1", "2", "3"]' \ + '["insert", "2", "2", "3"]' \ + '["commit"]' \ + '["print"]' \ + '["insert", "3", "1", "2"]' \ + '["print"]' \ + '["abort"]' \ + '["print"]']], + [dnl +insert 1 2 3: +insert 2 2 3: +commit: +print: +1: i=2, j=3 +2: i=2, j=3 +insert 3 1 2: +print: +1: i=2, j=3 +2: i=2, j=3 +3: i=1, j=2 +abort: +print: +1: i=2, j=3 +2: i=2, j=3], + [transaction]) + +OVSDB_CHECK_POSITIVE([modify, commit], + [[transact \ + '["insert", "1", "2", "3"]' \ + '["insert", "2", "2", "3"]' \ + '["commit"]' \ + '["print"]' \ + '["modify", "2", "5", "-1"]' \ + '["modify", "1", "-1", "4"]' \ + '["print"]' \ + '["commit"]' \ + '["print"]']], + [dnl +insert 1 2 3: +insert 2 2 3: +commit: +print: +1: i=2, j=3 +2: i=2, j=3 +modify 2 5 -1: +modify 1 -1 4: +print: +1: i=2, j=4 +2: i=5, j=3 +commit: +print: +1: i=2, j=4 +2: i=5, j=3], + [transaction]) + +OVSDB_CHECK_POSITIVE([modify, abort], + [[transact \ + '["insert", "1", "2", "3"]' \ + '["insert", "2", "2", "3"]' \ + '["commit"]' \ + '["print"]' \ + '["modify", "2", "5", "-1"]' \ + '["modify", "1", "-1", "4"]' \ + '["print"]' \ + '["abort"]' \ + '["print"]']], + [dnl +insert 1 2 3: +insert 2 2 3: +commit: +print: +1: i=2, j=3 +2: i=2, j=3 +modify 2 5 -1: +modify 1 -1 4: +print: +1: i=2, j=4 +2: i=5, j=3 +abort: +print: +1: i=2, j=3 +2: i=2, j=3], + [transaction]) + +OVSDB_CHECK_POSITIVE([delete, commit], + [[transact \ + '["insert", "1", "2", "3"]' \ + '["insert", "2", "2", "3"]' \ + '["commit"]' \ + '["print"]' \ + '["delete", "1"]' \ + '["print"]' \ + '["commit"]' \ + '["print"]']], + [dnl +insert 1 2 3: +insert 2 2 3: +commit: +print: +1: i=2, j=3 +2: i=2, j=3 +delete 1: +print: +2: i=2, j=3 +commit: +print: +2: i=2, j=3], + [transaction]) + +OVSDB_CHECK_POSITIVE([delete, abort], + [[transact \ + '["insert", "1", "2", "3"]' \ + '["insert", "2", "2", "3"]' \ + '["commit"]' \ + '["print"]' \ + '["delete", "1"]' \ + '["print"]' \ + '["abort"]' \ + '["print"]']], + [dnl +insert 1 2 3: +insert 2 2 3: +commit: +print: +1: i=2, j=3 +2: i=2, j=3 +delete 1: +print: +2: i=2, j=3 +abort: +print: +1: i=2, j=3 +2: i=2, j=3], + [transaction]) + +OVSDB_CHECK_POSITIVE([modify, delete, commit], + [[transact \ + '["insert", "1", "2", "3"]' \ + '["insert", "2", "2", "3"]' \ + '["commit"]' \ + '["print"]' \ + '["modify", "1", "5", "6"]' \ + '["delete", "1"]' \ + '["print"]' \ + '["commit"]' \ + '["print"]']], + [dnl +insert 1 2 3: +insert 2 2 3: +commit: +print: +1: i=2, j=3 +2: i=2, j=3 +modify 1 5 6: +delete 1: +print: +2: i=2, j=3 +commit: +print: +2: i=2, j=3], + [transaction]) + +OVSDB_CHECK_POSITIVE([modify, delete, abort], + [[transact \ + '["insert", "1", "2", "3"]' \ + '["insert", "2", "2", "3"]' \ + '["commit"]' \ + '["print"]' \ + '["modify", "1", "5", "6"]' \ + '["delete", "1"]' \ + '["print"]' \ + '["abort"]' \ + '["print"]']], + [dnl +insert 1 2 3: +insert 2 2 3: +commit: +print: +1: i=2, j=3 +2: i=2, j=3 +modify 1 5 6: +delete 1: +print: +2: i=2, j=3 +abort: +print: +1: i=2, j=3 +2: i=2, j=3], + [transaction]) + +OVSDB_CHECK_POSITIVE([insert, delete, commit], + [[transact \ + '["insert", "1", "2", "3"]' \ + '["insert", "2", "2", "3"]' \ + '["commit"]' \ + '["print"]' \ + '["insert", "3", "5", "6"]' \ + '["delete", "1"]' \ + '["delete", "3"]' \ + '["print"]' \ + '["commit"]' \ + '["print"]']], + [dnl +insert 1 2 3: +insert 2 2 3: +commit: +print: +1: i=2, j=3 +2: i=2, j=3 +insert 3 5 6: +delete 1: +delete 3: +print: +2: i=2, j=3 +commit: +print: +2: i=2, j=3], + [transaction]) + +OVSDB_CHECK_POSITIVE([insert, delete, abort], + [[transact \ + '["insert", "1", "2", "3"]' \ + '["insert", "2", "2", "3"]' \ + '["commit"]' \ + '["print"]' \ + '["insert", "3", "5", "6"]' \ + '["delete", "1"]' \ + '["delete", "3"]' \ + '["print"]' \ + '["abort"]' \ + '["print"]']], + [dnl +insert 1 2 3: +insert 2 2 3: +commit: +print: +1: i=2, j=3 +2: i=2, j=3 +insert 3 5 6: +delete 1: +delete 3: +print: +2: i=2, j=3 +abort: +print: +1: i=2, j=3 +2: i=2, j=3], + [transaction]) + + +OVSDB_CHECK_POSITIVE([insert, modify, delete, commit], + [[transact \ + '["insert", "1", "2", "3"]' \ + '["insert", "2", "2", "3"]' \ + '["commit"]' \ + '["print"]' \ + '["insert", "3", "5", "6"]' \ + '["delete", "1"]' \ + '["modify", "3", "7", "8"]' \ + '["delete", "3"]' \ + '["print"]' \ + '["commit"]' \ + '["print"]']], + [dnl +insert 1 2 3: +insert 2 2 3: +commit: +print: +1: i=2, j=3 +2: i=2, j=3 +insert 3 5 6: +delete 1: +modify 3 7 8: +delete 3: +print: +2: i=2, j=3 +commit: +print: +2: i=2, j=3], + [transaction]) + +OVSDB_CHECK_POSITIVE([insert, modify, delete, abort], + [[transact \ + '["insert", "1", "2", "3"]' \ + '["insert", "2", "2", "3"]' \ + '["commit"]' \ + '["print"]' \ + '["insert", "3", "5", "6"]' \ + '["delete", "1"]' \ + '["modify", "3", "7", "8"]' \ + '["delete", "3"]' \ + '["print"]' \ + '["abort"]' \ + '["print"]']], + [dnl +insert 1 2 3: +insert 2 2 3: +commit: +print: +1: i=2, j=3 +2: i=2, j=3 +insert 3 5 6: +delete 1: +modify 3 7 8: +delete 3: +print: +2: i=2, j=3 +abort: +print: +1: i=2, j=3 +2: i=2, j=3], + [transaction]) + diff --git a/tests/ovsdb-trigger.at b/tests/ovsdb-trigger.at new file mode 100644 index 00000000..5980d852 --- /dev/null +++ b/tests/ovsdb-trigger.at @@ -0,0 +1,173 @@ +AT_BANNER([OVSDB -- triggers]) + +# This is like OVSDB_CHECK_POSITIVE, except that UUIDs in the output +# are replaced by markers of the form where N is a number. The +# first unique UUID is replaced by <0>, the next by <1>, and so on. +# If a given UUID appears more than once it is always replaced by the +# same marker. +m4_define([OVSDB_CHECK_TRIGGER], + [AT_SETUP([$1]) + AT_KEYWORDS([ovsdb execute execution trigger positive $4]) + OVS_CHECK_LCOV([test-ovsdb trigger $2], [0], [stdout], []) + AT_CHECK([perl $srcdir/uuidfilt.pl stdout], [0], [$3]) + AT_CLEANUP]) + +OVSDB_CHECK_TRIGGER([trigger fires immediately], + [ORDINAL_SCHEMA [\ + '[{"op": "insert", + "table": "ordinals", + "row": {"number": 0, "name": "zero"}}, + {"op": "insert", + "table": "ordinals", + "row": {"number": 1, "name": "one"}}, + {"op": "wait", + "timeout": 10, + "table": "ordinals", + "where": [], + "columns": ["name", "number"], + "until": "==", + "rows": [{"name": "zero", "number": 0}, + {"name": "one", "number": 1}]}, + {"op": "insert", + "table": "ordinals", + "row": {"number": 2, "name": "two"}}]']], + [[t=0: trigger 0 (immediate): [{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{},{"uuid":["uuid","<2>"]}] +]]) + +OVSDB_CHECK_TRIGGER([trigger times out], + [ORDINAL_SCHEMA [\ + '[{"op": "insert", + "table": "ordinals", + "row": {"number": 0, "name": "zero"}}, + {"op": "insert", + "table": "ordinals", + "row": {"number": 1, "name": "one"}}, + {"op": "wait", + "timeout": 10, + "table": "ordinals", + "where": [], + "columns": ["name", "number"], + "until": "==", + "rows": [{"name": "zero", "number": 0}, + {"name": "one", "number": 1}, + {"name": "two", "number": 2}]}]' \ + '["advance", 10]']], + [[t=0: new trigger 0 +t=10: trigger 0 (delayed): [{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"details":"\"wait\" timed out after 10 ms","error":"timed out"}] +]]) + +OVSDB_CHECK_TRIGGER([trigger fires after delay], + [ORDINAL_SCHEMA [\ + '[{"op": "insert", + "table": "ordinals", + "row": {"number": 0, "name": "zero"}}, + {"op": "insert", + "table": "ordinals", + "row": {"number": 1, "name": "one"}}]' \ + '["advance", 5]' \ + '[{"op": "wait", + "timeout": 10, + "table": "ordinals", + "where": [], + "columns": ["name", "number"], + "until": "==", + "rows": [{"name": "zero", "number": 0}, + {"name": "one", "number": 1}, + {"name": "two", "number": 2}]}]' \ + '["advance", 5]' \ + '[{"op": "insert", + "table": "ordinals", + "row": {"number": 2, "name": "two"}}]']], + [[t=0: trigger 0 (immediate): [{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]}] +t=5: new trigger 1 +t=10: trigger 2 (immediate): [{"uuid":["uuid","<2>"]}] +t=10: trigger 1 (delayed): [{}] +]]) + +OVSDB_CHECK_TRIGGER([delayed trigger modifies database], + [ORDINAL_SCHEMA [\ + '[{"op": "insert", + "table": "ordinals", + "row": {"number": 0, "name": "zero"}}, + {"op": "insert", + "table": "ordinals", + "row": {"number": 1, "name": "one"}}]' \ + '["advance", 5]' \ + '[{"op": "wait", + "timeout": 10, + "table": "ordinals", + "where": [], + "columns": ["name", "number"], + "until": "==", + "rows": [{"name": "zero", "number": 0}, + {"name": "one", "number": 1}, + {"name": "two", "number": 2}]}, + {"op": "delete", + "table": "ordinals", + "where": [["number", "<", 2]]}]' \ + '["advance", 5]' \ + '[{"op": "insert", + "table": "ordinals", + "row": {"number": 2, "name": "two"}}]' \ + '["advance", 5]' \ + '[{"op": "select", + "table": "ordinals", + "where": []}]']], + [[t=0: trigger 0 (immediate): [{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]}] +t=5: new trigger 1 +t=10: trigger 2 (immediate): [{"uuid":["uuid","<2>"]}] +t=10: trigger 1 (delayed): [{},{"count":2}] +t=15: trigger 3 (immediate): [{"rows":[{"_uuid":["uuid","<2>"],"_version":["uuid","<3>"],"name":"two","number":2}]}] +]]) + +OVSDB_CHECK_TRIGGER([one delayed trigger wakes up another], + [ORDINAL_SCHEMA [\ + '[{"op": "insert", + "table": "ordinals", + "row": {"number": 0, "name": "zero"}}, + {"op": "insert", + "table": "ordinals", + "row": {"number": 1, "name": "one"}}]' \ + '["advance", 5]' \ + '[{"op": "wait", + "timeout": 10, + "table": "ordinals", + "where": [], + "columns": ["name", "number"], + "until": "==", + "rows": [{"name": "two", "number": 2}]}, + {"op": "delete", + "table": "ordinals", + "where": [["number", "==", 2]]}, + {"op": "insert", + "table": "ordinals", + "row": {"number": 3, "name": "three"}}]' \ + '[{"op": "wait", + "timeout": 10, + "table": "ordinals", + "where": [], + "columns": ["name", "number"], + "until": "==", + "rows": [{"name": "zero", "number": 0}, + {"name": "one", "number": 1}, + {"name": "two", "number": 2}]}, + {"op": "delete", + "table": "ordinals", + "where": [["number", "<", 2]]}]' \ + '["advance", 5]' \ + '[{"op": "insert", + "table": "ordinals", + "row": {"number": 2, "name": "two"}}]' \ + '["advance", 5]' \ + '[{"op": "select", + "table": "ordinals", + "where": []}]']], + [[t=0: trigger 0 (immediate): [{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]}] +t=5: new trigger 1 +t=5: new trigger 2 +t=10: trigger 3 (immediate): [{"uuid":["uuid","<2>"]}] +t=10: trigger 2 (delayed): [{},{"count":2}] +t=15: trigger 1 (delayed): [{},{"count":1},{"uuid":["uuid","<3>"]}] +t=15: trigger 4 (immediate): [{"rows":[{"_uuid":["uuid","<3>"],"_version":["uuid","<4>"],"name":"three","number":3}]}] +]]) + diff --git a/tests/ovsdb-types.at b/tests/ovsdb-types.at new file mode 100644 index 00000000..9a92a5ca --- /dev/null +++ b/tests/ovsdb-types.at @@ -0,0 +1,90 @@ +AT_BANNER([OVSDB -- atomic types]) + +OVSDB_CHECK_POSITIVE([integer], + [[parse-atomic-type '["integer"]' ]], ["integer"]) +OVSDB_CHECK_POSITIVE([real], + [[parse-atomic-type '["real"]' ]], ["real"]) +OVSDB_CHECK_POSITIVE([boolean], + [[parse-atomic-type '["boolean"]' ]], ["boolean"]) +OVSDB_CHECK_POSITIVE([string], + [[parse-atomic-type '["string"]' ]], ["string"]) +OVSDB_CHECK_POSITIVE([uuid], + [[parse-atomic-type '["uuid"]' ]], ["uuid"]) +OVSDB_CHECK_NEGATIVE([void is not a valid atomic-type], + [[parse-atomic-type '["void"]' ]], ["void" is not an atomic-type]) + +AT_BANNER([OVSDB -- simple types]) + +OVSDB_CHECK_POSITIVE([simple integer], + [[parse-type '["integer"]' ]], ["integer"]) +OVSDB_CHECK_POSITIVE([simple real], + [[parse-type '["real"]' ]], ["real"]) +OVSDB_CHECK_POSITIVE([simple boolean], + [[parse-type '["boolean"]' ]], ["boolean"]) +OVSDB_CHECK_POSITIVE([simple string], + [[parse-type '["string"]' ]], ["string"]) +OVSDB_CHECK_POSITIVE([simple uuid], + [[parse-type '["uuid"]' ]], ["uuid"]) +OVSDB_CHECK_POSITIVE([integer in object], + [[parse-type '{"key": "integer"}' ]], ["integer"]) +OVSDB_CHECK_POSITIVE([real in object with explicit min and max], + [[parse-type '{"key": "real", "min": 1, "max": 1}' ]], ["real"]) + +OVSDB_CHECK_NEGATIVE([key type is required], + [[parse-type '{}' ]], [Required 'key' member is missing.]) +OVSDB_CHECK_NEGATIVE([void is not a valid type], + [[parse-type '["void"]' ]], ["void" is not an atomic-type]) + +AT_BANNER([OVSDB -- set types]) + +OVSDB_CHECK_POSITIVE([optional boolean], + [[parse-type '{"key": "boolean", "min": 0}' ]], + [[{"key":"boolean","min":0}]], + [set]) +OVSDB_CHECK_POSITIVE([set of 1 to 3 uuids], + [[parse-type '{"key": "uuid", "min": 1, "max": 3}' ]], + [[{"key":"uuid","max":3}]]) +OVSDB_CHECK_POSITIVE([set of 0 to 3 strings], + [[parse-type '{"key": "string", "min": 0, "max": 3}' ]], + [[{"key":"string","max":3,"min":0}]]) +OVSDB_CHECK_POSITIVE([set of 0 or more integers], + [[parse-type '{"key": "integer", "min": 0, "max": "unlimited"}']], + [[{"key":"integer","max":"unlimited","min":0}]]) +OVSDB_CHECK_POSITIVE([set of 10 or more reals], + [[parse-type '{"key": "real", "min": 10, "max": "unlimited"}']], + [[{"key":"real","max":"unlimited","min":10}]]) + +OVSDB_CHECK_NEGATIVE([set max cannot be less than min], + [[parse-type '{"key": "real", "min": 5, "max": 3}' ]], + [ovsdb type fails constraint checks]) +OVSDB_CHECK_NEGATIVE([set max cannot be negative], + [[parse-type '{"key": "real", "max": -1}' ]], + [bad min or max value]) +OVSDB_CHECK_NEGATIVE([set min cannot be negative], + [[parse-type '{"key": "real", "min": -1}' ]], + [bad min or max value]) + +AT_BANNER([OVSDB -- map types]) + +OVSDB_CHECK_POSITIVE([map of 1 integer to boolean], + [[parse-type '{"key": "integer", "value": "boolean"}' ]], + [[{"key":"integer","value":"boolean"}]]) +OVSDB_CHECK_POSITIVE([map of 1 boolean to integer, explicit min and max], + [[parse-type '{"key": "boolean", "value": "integer", "min": 1, "max": 1}' ]], + [[{"key":"boolean","value":"integer"}]]) +OVSDB_CHECK_POSITIVE([map of 2 to 5 uuid to real], + [[parse-type '{"key": "uuid", "value": "real", "min": 2, "max": 5}' ]], + [[{"key":"uuid","max":5,"min":2,"value":"real"}]]) +OVSDB_CHECK_POSITIVE([map of 0 to 10 string to uuid], + [[parse-type '{"key": "string", "value": "uuid", "min": 0, "max": 10}' ]], + [[{"key":"string","max":10,"min":0,"value":"uuid"}]]) +OVSDB_CHECK_POSITIVE([map of 10 to 20 real to string], + [[parse-type '{"key": "real", "value": "string", "min": 10, "max": 20}' ]], + [[{"key":"real","max":20,"min":10,"value":"string"}]]) +OVSDB_CHECK_POSITIVE([map of 20 or more string to real], + [[parse-type '{"key": "string", "value": "real", "min": 20, "max": "unlimited"}' ]], + [[{"key":"string","max":"unlimited","min":20,"value":"real"}]]) + +OVSDB_CHECK_NEGATIVE([map key type is required], + [[parse-type '{"value": "integer"}' ]], + [Required 'key' member is missing.]) diff --git a/tests/ovsdb.at b/tests/ovsdb.at new file mode 100644 index 00000000..0e0e63b0 --- /dev/null +++ b/tests/ovsdb.at @@ -0,0 +1,35 @@ +m4_define([OVSDB_CHECK_POSITIVE], + [AT_SETUP([$1]) + m4_if([$5], [], [], + [AT_XFAIL_IF([m4_version_prereq([$5], [false], [true])])]) + AT_KEYWORDS([ovsdb positive $4]) + OVS_CHECK_LCOV([test-ovsdb $2], [0], [$3 +], []) + AT_CLEANUP]) + +m4_define([OVSDB_CHECK_NEGATIVE], + [AT_SETUP([$1]) + AT_KEYWORDS([ovsdb negative $4]) + OVS_CHECK_LCOV([test-ovsdb $2], [1], [], [stderr]) + m4_assert(m4_len([$3])) + AT_CHECK( + [if grep -F -e "AS_ESCAPE([$3])" stderr + then + : + else + exit 99 + fi], + [0], [ignore], [ignore]) + AT_CLEANUP]) + +m4_include([tests/ovsdb-file.at]) +m4_include([tests/ovsdb-types.at]) +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-condition.at]) +m4_include([tests/ovsdb-query.at]) +m4_include([tests/ovsdb-transaction.at]) +m4_include([tests/ovsdb-execution.at]) +m4_include([tests/ovsdb-trigger.at]) diff --git a/tests/test-ovsdb.c b/tests/test-ovsdb.c new file mode 100644 index 00000000..bee1818b --- /dev/null +++ b/tests/test-ovsdb.c @@ -0,0 +1,1229 @@ +/* + * Copyright (c) 2009 Nicira Networks. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include +#include +#include +#include + +#include "command-line.h" +#include "json.h" +#include "ovsdb-data.h" +#include "ovsdb-error.h" +#include "ovsdb-types.h" +#include "ovsdb/column.h" +#include "ovsdb/condition.h" +#include "ovsdb/file.h" +#include "ovsdb/ovsdb.h" +#include "ovsdb/query.h" +#include "ovsdb/row.h" +#include "ovsdb/table.h" +#include "ovsdb/transaction.h" +#include "ovsdb/trigger.h" +#include "poll-loop.h" +#include "svec.h" +#include "timeval.h" +#include "util.h" +#include "vlog.h" + +static struct command all_commands[]; + +static void usage(void) NO_RETURN; +static void parse_options(int argc, char *argv[]); + +int +main(int argc, char *argv[]) +{ + set_program_name(argv[0]); + time_init(); + vlog_init(); + parse_options(argc, argv); + run_command(argc - optind, argv + optind, all_commands); + return 0; +} + +static void +parse_options(int argc, char *argv[]) +{ + static struct option long_options[] = { + {"verbose", optional_argument, 0, 'v'}, + {"help", no_argument, 0, 'h'}, + {0, 0, 0, 0}, + }; + char *short_options = long_options_to_short_options(long_options); + + for (;;) { + int c = getopt_long(argc, argv, short_options, long_options, NULL); + if (c == -1) { + break; + } + + switch (c) { + case 'h': + usage(); + + case 'v': + vlog_set_verbosity(optarg); + break; + + case '?': + exit(EXIT_FAILURE); + + default: + abort(); + } + } + free(short_options); +} + +static void +usage(void) +{ + printf("%s: Open vSwitch database test utility\n" + "usage: %s [OPTIONS] COMMAND [ARG...]\n\n" + " file-io FILE FLAGS COMMAND...\n" + " open FILE with FLAGS, run COMMANDs\n" + " parse-atomic-type TYPE\n" + " parse TYPE as OVSDB atomic type, and re-serialize\n" + " parse-type JSON\n" + " parse JSON as OVSDB type, and re-serialize\n" + " parse-atoms TYPE ATOM...\n" + " parse ATOMs as atoms of given TYPE, and re-serialize\n" + " sort-atoms TYPE ATOM...\n" + " print ATOMs in sorted order, and re-serialize\n" + " parse-data TYPE DATUM...\n" + " parse DATUMs as data of given TYPE, and re-serialize\n" + " parse-column NAME OBJECT\n" + " parse column NAME with info OBJECT, and re-serialize\n" + " parse-table NAME OBJECT\n" + " parse table NAME with info OBJECT\n" + " parse-row TABLE ROW..., and re-serialize\n" + " parse each ROW of defined TABLE\n" + " compare-row TABLE ROW...\n" + " mutually compare all of the ROWs, print those that are equal\n" + " parse-conditions TABLE CONDITION...\n" + " parse each CONDITION on TABLE, and re-serialize\n" + " evaluate-conditions TABLE [CONDITION,...] [ROW,...]\n" + " test CONDITIONS on TABLE against each ROW, print results\n" + " query TABLE [ROW,...] [CONDITION,...]\n" + " add each ROW to TABLE, then query and print the rows that\n" + " satisfy each CONDITION.\n" + " 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" + " transact COMMAND\n" + " execute each specified transactional COMMAND:\n" + " commit\n" + " abort\n" + " insert UUID I J\n" + " delete UUID\n" + " modify UUID I J\n" + " print\n" + " execute SCHEMA TRANSACTION...\n" + " executes each TRANSACTION on an initially empty database\n" + " the specified SCHEMA\n" + " trigger SCHEMA TRANSACTION...\n" + " executes each TRANSACTION on an initially empty database\n" + " the specified SCHEMA. A TRANSACTION of the form\n" + " [\"advance\", NUMBER] advances NUMBER milliseconds in\n" + " simulated time, for causing triggers to time out.\n", + program_name, program_name); + vlog_usage(); + printf("\nOther options:\n" + " -h, --help display this help message\n"); + exit(EXIT_SUCCESS); +} + +/* Command helper functions. */ + +static struct json * +parse_json(const char *s) +{ + struct json *json = json_from_string(s); + if (json->type == JSON_STRING) { + ovs_fatal(0, "\"%s\": %s", s, json->u.string); + } + return json; +} + +static struct json * +unbox_json(struct json *json) +{ + if (json->type == JSON_ARRAY && json->u.array.n == 1) { + struct json *inner = json->u.array.elems[0]; + json->u.array.elems[0] = NULL; + json_destroy(json); + return inner; + } else { + return json; + } +} + +static void +print_and_free_json(struct json *json) +{ + char *string = json_to_string(json, JSSF_SORT); + json_destroy(json); + puts(string); + free(string); +} + +static void +check_ovsdb_error(struct ovsdb_error *error) +{ + if (error) { + ovs_fatal(0, "%s", ovsdb_error_to_string(error)); + } +} + +/* Command implementations. */ + +static void +do_file_io(int argc, char *argv[]) +{ + const char *name = argv[1]; + char *mode = argv[2]; + + struct ovsdb_error *error; + struct ovsdb_file *file; + char *save_ptr = NULL; + const char *token; + int flags; + int i; + + for (flags = 0, token = strtok_r(mode, " |", &save_ptr); token != NULL; + token = strtok_r(NULL, " |", &save_ptr)) + { + if (!strcmp(token, "O_RDONLY")) { + flags |= O_RDONLY; + } else if (!strcmp(token, "O_RDWR")) { + flags |= O_RDWR; + } else if (!strcmp(token, "O_TRUNC")) { + flags |= O_TRUNC; + } else if (!strcmp(token, "O_CREAT")) { + flags |= O_CREAT; + } else if (!strcmp(token, "O_EXCL")) { + flags |= O_EXCL; + } else if (!strcmp(token, "O_TRUNC")) { + flags |= O_TRUNC; + } + } + + check_ovsdb_error(ovsdb_file_open(name, flags, &file)); + printf("%s: open successful\n", name); + + for (i = 3; i < argc; i++) { + const char *command = argv[i]; + if (!strcmp(command, "read")) { + struct json *json; + + error = ovsdb_file_read(file, &json); + if (!error) { + printf("%s: read: ", name); + if (json) { + print_and_free_json(json); + } else { + printf("end of file\n"); + } + continue; + } + } else if (!strncmp(command, "write:", 6)) { + struct json *json = parse_json(command + 6); + error = ovsdb_file_write(file, json); + json_destroy(json); + } else if (!strcmp(command, "commit")) { + error = ovsdb_file_commit(file); + } else { + ovs_fatal(0, "unknown file-io command \"%s\"", command); + } + if (error) { + char *s = ovsdb_error_to_string(error); + printf("%s: %s failed: %s\n", name, command, s); + free(s); + } else { + printf("%s: %s successful\n", name, command); + } + } + + ovsdb_file_close(file); +} + +static void +do_parse_atomic_type(int argc UNUSED, char *argv[]) +{ + enum ovsdb_atomic_type type; + struct json *json; + + json = unbox_json(parse_json(argv[1])); + check_ovsdb_error(ovsdb_atomic_type_from_json(&type, json)); + json_destroy(json); + print_and_free_json(ovsdb_atomic_type_to_json(type)); +} + +static void +do_parse_type(int argc UNUSED, char *argv[]) +{ + struct ovsdb_type type; + struct json *json; + + json = unbox_json(parse_json(argv[1])); + check_ovsdb_error(ovsdb_type_from_json(&type, json)); + json_destroy(json); + print_and_free_json(ovsdb_type_to_json(&type)); +} + +static void +do_parse_atoms(int argc, char *argv[]) +{ + enum ovsdb_atomic_type type; + struct json *json; + int i; + + json = unbox_json(parse_json(argv[1])); + check_ovsdb_error(ovsdb_atomic_type_from_json(&type, json)); + json_destroy(json); + + for (i = 2; i < argc; i++) { + union ovsdb_atom atom; + + json = unbox_json(parse_json(argv[i])); + check_ovsdb_error(ovsdb_atom_from_json(&atom, type, json, NULL)); + json_destroy(json); + + print_and_free_json(ovsdb_atom_to_json(&atom, type)); + + ovsdb_atom_destroy(&atom, type); + } +} + +static void +do_parse_data(int argc, char *argv[]) +{ + struct ovsdb_type type; + struct json *json; + int i; + + json = unbox_json(parse_json(argv[1])); + check_ovsdb_error(ovsdb_type_from_json(&type, json)); + json_destroy(json); + + for (i = 2; i < argc; i++) { + struct ovsdb_datum datum; + + json = unbox_json(parse_json(argv[i])); + check_ovsdb_error(ovsdb_datum_from_json(&datum, &type, json, NULL)); + json_destroy(json); + + print_and_free_json(ovsdb_datum_to_json(&datum, &type)); + + ovsdb_datum_destroy(&datum, &type); + } +} + +static enum ovsdb_atomic_type compare_atoms_atomic_type; + +static int +compare_atoms(const void *a_, const void *b_) +{ + const union ovsdb_atom *a = a_; + const union ovsdb_atom *b = b_; + + return ovsdb_atom_compare_3way(a, b, compare_atoms_atomic_type); +} + +static void +do_sort_atoms(int argc UNUSED, char *argv[]) +{ + enum ovsdb_atomic_type type; + union ovsdb_atom *atoms; + struct json *json, **json_atoms; + size_t n_atoms; + int i; + + json = unbox_json(parse_json(argv[1])); + check_ovsdb_error(ovsdb_atomic_type_from_json(&type, json)); + json_destroy(json); + + json = unbox_json(parse_json(argv[2])); + if (json->type != JSON_ARRAY) { + ovs_fatal(0, "second argument must be array"); + } + + /* Convert JSON atoms to internal representation. */ + n_atoms = json->u.array.n; + atoms = xmalloc(n_atoms * sizeof *atoms); + for (i = 0; i < n_atoms; i++) { + check_ovsdb_error(ovsdb_atom_from_json(&atoms[i], type, + json->u.array.elems[i], NULL)); + } + json_destroy(json); + + /* Sort atoms. */ + compare_atoms_atomic_type = type; + qsort(atoms, n_atoms, sizeof *atoms, compare_atoms); + + /* Convert internal representation back to JSON. */ + json_atoms = xmalloc(n_atoms * sizeof *json_atoms); + for (i = 0; i < n_atoms; i++) { + json_atoms[i] = ovsdb_atom_to_json(&atoms[i], type); + ovsdb_atom_destroy(&atoms[i], type); + } + print_and_free_json(json_array_create(json_atoms, n_atoms)); +} + +static void +do_parse_column(int argc UNUSED, char *argv[]) +{ + struct ovsdb_column *column; + struct json *json; + + json = parse_json(argv[2]); + check_ovsdb_error(ovsdb_column_from_json(json, argv[1], &column)); + json_destroy(json); + print_and_free_json(ovsdb_column_to_json(column)); + ovsdb_column_destroy(column); +} + +static void +do_parse_table(int argc UNUSED, char *argv[]) +{ + struct ovsdb_table_schema *ts; + struct json *json; + + json = parse_json(argv[2]); + check_ovsdb_error(ovsdb_table_schema_from_json(json, argv[1], &ts)); + json_destroy(json); + print_and_free_json(ovsdb_table_schema_to_json(ts)); + ovsdb_table_schema_destroy(ts); +} + +static void +do_parse_rows(int argc, char *argv[]) +{ + struct ovsdb_column_set all_columns; + struct ovsdb_table_schema *ts; + struct ovsdb_table *table; + struct json *json; + int i; + + json = unbox_json(parse_json(argv[1])); + check_ovsdb_error(ovsdb_table_schema_from_json(json, "mytable", &ts)); + json_destroy(json); + + table = ovsdb_table_create(ts); + ovsdb_column_set_init(&all_columns); + ovsdb_column_set_add_all(&all_columns, table); + + for (i = 2; i < argc; i++) { + struct ovsdb_column_set columns; + struct ovsdb_row *row; + + ovsdb_column_set_init(&columns); + row = ovsdb_row_create(table); + + json = unbox_json(parse_json(argv[i])); + check_ovsdb_error(ovsdb_row_from_json(row, json, NULL, &columns)); + json_destroy(json); + + print_and_free_json(ovsdb_row_to_json(row, &all_columns)); + + if (columns.n_columns) { + struct svec names; + size_t j; + char *s; + + svec_init(&names); + for (j = 0; j < columns.n_columns; j++) { + svec_add(&names, columns.columns[j]->name); + } + svec_sort(&names); + s = svec_join(&names, ", ", ""); + puts(s); + free(s); + svec_destroy(&names); + } else { + printf("\n"); + } + + ovsdb_column_set_destroy(&columns); + ovsdb_row_destroy(row); + } + + ovsdb_column_set_destroy(&all_columns); + ovsdb_table_destroy(table); /* Also destroys 'ts'. */ +} + +static void +do_compare_rows(int argc, char *argv[]) +{ + struct ovsdb_column_set all_columns; + struct ovsdb_table_schema *ts; + struct ovsdb_table *table; + struct ovsdb_row **rows; + struct json *json; + char **names; + int n_rows; + int i, j; + + json = unbox_json(parse_json(argv[1])); + check_ovsdb_error(ovsdb_table_schema_from_json(json, "mytable", &ts)); + json_destroy(json); + + table = ovsdb_table_create(ts); + ovsdb_column_set_init(&all_columns); + ovsdb_column_set_add_all(&all_columns, table); + + n_rows = argc - 2; + rows = xmalloc(sizeof *rows * n_rows); + names = xmalloc(sizeof *names * n_rows); + for (i = 0; i < n_rows; i++) { + rows[i] = ovsdb_row_create(table); + + json = parse_json(argv[i + 2]); + if (json->type != JSON_ARRAY || json->u.array.n != 2 + || json->u.array.elems[0]->type != JSON_STRING) { + ovs_fatal(0, "\"%s\" does not have expected form " + "[\"name\", {data}]", argv[i]); + } + names[i] = xstrdup(json->u.array.elems[0]->u.string); + check_ovsdb_error(ovsdb_row_from_json(rows[i], json->u.array.elems[1], + NULL, NULL)); + json_destroy(json); + } + for (i = 0; i < n_rows; i++) { + uint32_t i_hash = ovsdb_row_hash_columns(rows[i], &all_columns, 0); + for (j = i + 1; j < n_rows; j++) { + uint32_t j_hash = ovsdb_row_hash_columns(rows[j], &all_columns, 0); + if (ovsdb_row_equal_columns(rows[i], rows[j], &all_columns)) { + printf("%s == %s\n", names[i], names[j]); + if (i_hash != j_hash) { + printf("but hash(%s) != hash(%s)\n", names[i], names[j]); + abort(); + } + } else if (i_hash == j_hash) { + printf("hash(%s) == hash(%s)\n", names[i], names[j]); + } + } + } + free(rows); + free(names); + + ovsdb_column_set_destroy(&all_columns); + ovsdb_table_destroy(table); /* Also destroys 'ts'. */ +} + +static void +do_parse_conditions(int argc, char *argv[]) +{ + struct ovsdb_table_schema *ts; + struct json *json; + int exit_code = 0; + int i; + + json = unbox_json(parse_json(argv[1])); + check_ovsdb_error(ovsdb_table_schema_from_json(json, "mytable", &ts)); + json_destroy(json); + + for (i = 2; i < argc; i++) { + struct ovsdb_condition cnd; + struct ovsdb_error *error; + + json = parse_json(argv[i]); + error = ovsdb_condition_from_json(ts, json, NULL, &cnd); + if (!error) { + print_and_free_json(ovsdb_condition_to_json(&cnd)); + } else { + char *s = ovsdb_error_to_string(error); + ovs_error(0, "%s", s); + free(s); + ovsdb_error_destroy(error); + exit_code = 1; + } + json_destroy(json); + + ovsdb_condition_destroy(&cnd); + } + ovsdb_table_schema_destroy(ts); + + exit(exit_code); +} + +static void +do_evaluate_conditions(int argc UNUSED, char *argv[]) +{ + struct ovsdb_table_schema *ts; + struct ovsdb_table *table; + struct ovsdb_condition *conditions; + size_t n_conditions; + struct ovsdb_row **rows; + size_t n_rows; + struct json *json; + size_t i, j; + + /* Parse table schema, create table. */ + json = unbox_json(parse_json(argv[1])); + check_ovsdb_error(ovsdb_table_schema_from_json(json, "mytable", &ts)); + json_destroy(json); + + table = ovsdb_table_create(ts); + + /* Parse conditions. */ + json = parse_json(argv[2]); + if (json->type != JSON_ARRAY) { + ovs_fatal(0, "CONDITION argument is not JSON array"); + } + n_conditions = json->u.array.n; + conditions = xmalloc(n_conditions * sizeof *conditions); + for (i = 0; i < n_conditions; i++) { + check_ovsdb_error(ovsdb_condition_from_json(ts, json->u.array.elems[i], + NULL, &conditions[i])); + } + json_destroy(json); + + /* Parse rows. */ + json = parse_json(argv[3]); + if (json->type != JSON_ARRAY) { + ovs_fatal(0, "ROW argument is not JSON array"); + } + n_rows = json->u.array.n; + rows = xmalloc(n_rows * sizeof *rows); + for (i = 0; i < n_rows; i++) { + rows[i] = ovsdb_row_create(table); + check_ovsdb_error(ovsdb_row_from_json(rows[i], json->u.array.elems[i], + NULL, NULL)); + } + json_destroy(json); + + for (i = 0; i < n_conditions; i++) { + printf("condition %2d:", i); + for (j = 0; j < n_rows; j++) { + bool result = ovsdb_condition_evaluate(rows[j], &conditions[i]); + if (j % 5 == 0) { + putchar(' '); + } + putchar(result ? 'T' : '-'); + } + printf("\n"); + } + + for (i = 0; i < n_conditions; i++) { + ovsdb_condition_destroy(&conditions[i]); + } + for (i = 0; i < n_rows; i++) { + ovsdb_row_destroy(rows[i]); + } + ovsdb_table_destroy(table); /* Also destroys 'ts'. */ +} + +struct do_query_cbdata { + struct uuid *row_uuids; + int *counts; + size_t n_rows; +}; + +static bool +do_query_cb(const struct ovsdb_row *row, void *cbdata_) +{ + struct do_query_cbdata *cbdata = cbdata_; + size_t i; + + for (i = 0; i < cbdata->n_rows; i++) { + if (uuid_equals(ovsdb_row_get_uuid(row), &cbdata->row_uuids[i])) { + cbdata->counts[i]++; + } + } + + return true; +} + +static void +do_query(int argc UNUSED, char *argv[]) +{ + struct do_query_cbdata cbdata; + struct ovsdb_table_schema *ts; + struct ovsdb_table *table; + struct json *json; + int exit_code = 0; + size_t i; + + /* Parse table schema, create table. */ + json = unbox_json(parse_json(argv[1])); + check_ovsdb_error(ovsdb_table_schema_from_json(json, "mytable", &ts)); + json_destroy(json); + + table = ovsdb_table_create(ts); + + /* Parse rows, add to table. */ + json = parse_json(argv[2]); + if (json->type != JSON_ARRAY) { + ovs_fatal(0, "ROW argument is not JSON array"); + } + cbdata.n_rows = json->u.array.n; + cbdata.row_uuids = xmalloc(cbdata.n_rows * sizeof *cbdata.row_uuids); + cbdata.counts = xmalloc(cbdata.n_rows * sizeof *cbdata.counts); + for (i = 0; i < cbdata.n_rows; i++) { + struct ovsdb_row *row = ovsdb_row_create(table); + uuid_generate(ovsdb_row_get_uuid_rw(row)); + check_ovsdb_error(ovsdb_row_from_json(row, json->u.array.elems[i], + NULL, NULL)); + if (ovsdb_table_get_row(table, ovsdb_row_get_uuid(row))) { + ovs_fatal(0, "duplicate UUID "UUID_FMT" in table", + UUID_ARGS(ovsdb_row_get_uuid(row))); + } + cbdata.row_uuids[i] = *ovsdb_row_get_uuid(row); + ovsdb_table_put_row(table, row); + } + json_destroy(json); + + /* Parse conditions and execute queries. */ + json = parse_json(argv[3]); + if (json->type != JSON_ARRAY) { + ovs_fatal(0, "CONDITION argument is not JSON array"); + } + for (i = 0; i < json->u.array.n; i++) { + struct ovsdb_condition cnd; + size_t j; + + check_ovsdb_error(ovsdb_condition_from_json(ts, json->u.array.elems[i], + NULL, &cnd)); + + memset(cbdata.counts, 0, cbdata.n_rows * sizeof *cbdata.counts); + ovsdb_query(table, &cnd, do_query_cb, &cbdata); + + printf("query %2d:", i); + for (j = 0; j < cbdata.n_rows; j++) { + if (j % 5 == 0) { + putchar(' '); + } + if (cbdata.counts[j]) { + printf("%d", cbdata.counts[j]); + if (cbdata.counts[j] > 1) { + /* Dup! */ + exit_code = 1; + } + } else { + putchar('-'); + } + } + putchar('\n'); + + ovsdb_condition_destroy(&cnd); + } + json_destroy(json); + + ovsdb_table_destroy(table); /* Also destroys 'ts'. */ + + exit(exit_code); +} + +struct do_query_distinct_class { + struct ovsdb_row *example; + int count; +}; + +struct do_query_distinct_row { + struct uuid uuid; + struct do_query_distinct_class *class; +}; + +static void +do_query_distinct(int argc UNUSED, char *argv[]) +{ + struct ovsdb_column_set columns; + struct ovsdb_table_schema *ts; + struct ovsdb_table *table; + struct do_query_distinct_row *rows; + size_t n_rows; + struct do_query_distinct_class *classes; + size_t n_classes; + struct json *json; + int exit_code = 0; + size_t i, j, k; + + /* Parse table schema, create table. */ + json = unbox_json(parse_json(argv[1])); + check_ovsdb_error(ovsdb_table_schema_from_json(json, "mytable", &ts)); + json_destroy(json); + + table = ovsdb_table_create(ts); + + /* Parse column set. */ + json = parse_json(argv[4]); + ovsdb_column_set_from_json(json, table, &columns); + json_destroy(json); + + /* Parse rows, add to table. */ + json = parse_json(argv[2]); + if (json->type != JSON_ARRAY) { + ovs_fatal(0, "ROW argument is not JSON array"); + } + n_rows = json->u.array.n; + rows = xmalloc(n_rows * sizeof *rows); + classes = xmalloc(n_rows * sizeof *classes); + n_classes = 0; + for (i = 0; i < n_rows; i++) { + struct ovsdb_row *row; + size_t j; + + /* Parse row. */ + row = ovsdb_row_create(table); + uuid_generate(ovsdb_row_get_uuid_rw(row)); + check_ovsdb_error(ovsdb_row_from_json(row, json->u.array.elems[i], + NULL, NULL)); + + /* Initialize row and find equivalence class. */ + rows[i].uuid = *ovsdb_row_get_uuid(row); + rows[i].class = NULL; + for (j = 0; j < n_classes; j++) { + if (ovsdb_row_equal_columns(row, classes[j].example, &columns)) { + rows[i].class = &classes[j]; + break; + } + } + if (!rows[i].class) { + rows[i].class = &classes[n_classes]; + classes[n_classes].example = ovsdb_row_clone(row); + n_classes++; + } + + /* Add row to table. */ + if (ovsdb_table_get_row(table, ovsdb_row_get_uuid(row))) { + ovs_fatal(0, "duplicate UUID "UUID_FMT" in table", + UUID_ARGS(ovsdb_row_get_uuid(row))); + } + ovsdb_table_put_row(table, row); + + } + json_destroy(json); + + /* Parse conditions and execute queries. */ + json = parse_json(argv[3]); + if (json->type != JSON_ARRAY) { + ovs_fatal(0, "CONDITION argument is not JSON array"); + } + for (i = 0; i < json->u.array.n; i++) { + struct ovsdb_row_set results; + struct ovsdb_condition cnd; + + check_ovsdb_error(ovsdb_condition_from_json(ts, json->u.array.elems[i], + NULL, &cnd)); + + for (j = 0; j < n_classes; j++) { + classes[j].count = 0; + } + ovsdb_row_set_init(&results); + ovsdb_query_distinct(table, &cnd, &columns, &results); + for (j = 0; j < results.n_rows; j++) { + for (k = 0; k < n_rows; k++) { + if (uuid_equals(ovsdb_row_get_uuid(results.rows[j]), + &rows[k].uuid)) { + rows[k].class->count++; + } + } + } + ovsdb_row_set_destroy(&results); + + printf("query %2d:", i); + for (j = 0; j < n_rows; j++) { + int count = rows[j].class->count; + + if (j % 5 == 0) { + putchar(' '); + } + if (count > 1) { + /* Dup! */ + printf("%d", count); + exit_code = 1; + } else if (count == 1) { + putchar("abcdefghijklmnopqrstuvwxyz"[rows[j].class - classes]); + } else { + putchar('-'); + } + } + putchar('\n'); + + ovsdb_condition_destroy(&cnd); + } + json_destroy(json); + + ovsdb_table_destroy(table); /* Also destroys 'ts'. */ + + exit(exit_code); +} + +static void +do_execute(int argc UNUSED, char *argv[]) +{ + struct ovsdb_schema *schema; + struct json *json; + struct ovsdb *db; + int i; + + /* Create database. */ + json = parse_json(argv[1]); + check_ovsdb_error(ovsdb_schema_from_json(json, &schema)); + json_destroy(json); + db = ovsdb_create(NULL, schema); + + for (i = 2; i < argc; i++) { + struct json *params, *result; + char *s; + + params = parse_json(argv[i]); + result = ovsdb_execute(db, params, 0, NULL); + s = json_to_string(result, JSSF_SORT); + printf("%s\n", s); + json_destroy(params); + json_destroy(result); + } + + ovsdb_destroy(db); +} + +struct test_trigger { + struct ovsdb_trigger trigger; + int number; +}; + +static void +do_trigger_dump(struct test_trigger *t, long long int now, const char *title) +{ + struct json *result; + char *s; + + result = ovsdb_trigger_steal_result(&t->trigger); + s = json_to_string(result, JSSF_SORT); + printf("t=%lld: trigger %d (%s): %s\n", now, t->number, title, s); + json_destroy(result); + ovsdb_trigger_destroy(&t->trigger); + free(t); +} + +static void +do_trigger(int argc UNUSED, char *argv[]) +{ + struct ovsdb_schema *schema; + struct list completions; + struct json *json; + struct ovsdb *db; + long long int now; + int number; + int i; + + /* Create database. */ + json = parse_json(argv[1]); + check_ovsdb_error(ovsdb_schema_from_json(json, &schema)); + json_destroy(json); + db = ovsdb_create(NULL, schema); + + list_init(&completions); + now = 0; + number = 0; + for (i = 2; i < argc; i++) { + struct json *params = parse_json(argv[i]); + if (params->type == JSON_ARRAY + && json_array(params)->n == 2 + && json_array(params)->elems[0]->type == JSON_STRING + && !strcmp(json_string(json_array(params)->elems[0]), "advance") + && json_array(params)->elems[1]->type == JSON_INTEGER) { + now += json_integer(json_array(params)->elems[1]); + json_destroy(params); + } else { + struct test_trigger *t = xmalloc(sizeof *t); + ovsdb_trigger_init(db, &t->trigger, params, &completions, now); + t->number = number++; + if (ovsdb_trigger_is_complete(&t->trigger)) { + do_trigger_dump(t, now, "immediate"); + } else { + printf("t=%lld: new trigger %d\n", now, t->number); + } + } + + ovsdb_trigger_run(db, now); + while (!list_is_empty(&completions)) { + do_trigger_dump(CONTAINER_OF(list_pop_front(&completions), + struct test_trigger, trigger.node), + now, "delayed"); + } + + ovsdb_trigger_wait(db, now); + poll_immediate_wake(); + poll_block(); + } + + ovsdb_destroy(db); +} + +static void +do_help(int argc UNUSED, char *argv[] UNUSED) +{ + usage(); +} + +/* "transact" command. */ + +static struct ovsdb *do_transact_db; +static struct ovsdb_txn *do_transact_txn; +static struct ovsdb_table *do_transact_table; + +static void +do_transact_commit(int argc UNUSED, char *argv[] UNUSED) +{ + ovsdb_txn_commit(do_transact_txn); + do_transact_txn = NULL; +} + +static void +do_transact_abort(int argc UNUSED, char *argv[] UNUSED) +{ + ovsdb_txn_abort(do_transact_txn); + do_transact_txn = NULL; +} + +static void +uuid_from_integer(int integer, struct uuid *uuid) +{ + uuid_zero(uuid); + uuid->parts[3] = integer; +} + +static const struct ovsdb_row * +do_transact_find_row(const char *uuid_string) +{ + const struct ovsdb_row *row; + struct uuid uuid; + + uuid_from_integer(atoi(uuid_string), &uuid); + row = ovsdb_table_get_row(do_transact_table, &uuid); + if (!row) { + ovs_fatal(0, "table does not contain row with UUID "UUID_FMT, + UUID_ARGS(&uuid)); + } + return row; +} + +static void +do_transact_set_integer(struct ovsdb_row *row, const char *column_name, + int integer) +{ + if (integer != -1) { + const struct ovsdb_column *column; + + column = ovsdb_table_schema_get_column(do_transact_table->schema, + column_name); + row->fields[column->index].keys[0].integer = integer; + } +} + +static int +do_transact_get_integer(const struct ovsdb_row *row, const char *column_name) +{ + const struct ovsdb_column *column; + + column = ovsdb_table_schema_get_column(do_transact_table->schema, + column_name); + return row->fields[column->index].keys[0].integer; +} + +static void +do_transact_set_i_j(struct ovsdb_row *row, + const char *i_string, const char *j_string) +{ + do_transact_set_integer(row, "i", atoi(i_string)); + do_transact_set_integer(row, "j", atoi(j_string)); +} + +static void +do_transact_insert(int argc UNUSED, char *argv[] UNUSED) +{ + struct ovsdb_row *row; + struct uuid *uuid; + + row = ovsdb_row_create(do_transact_table); + + /* Set UUID. */ + uuid = ovsdb_row_get_uuid_rw(row); + uuid_from_integer(atoi(argv[1]), uuid); + if (ovsdb_table_get_row(do_transact_table, uuid)) { + ovs_fatal(0, "table already contains row with UUID "UUID_FMT, + UUID_ARGS(uuid)); + } + + do_transact_set_i_j(row, argv[2], argv[3]); + + /* Insert row. */ + ovsdb_txn_row_insert(do_transact_txn, row); +} + +static void +do_transact_delete(int argc UNUSED, char *argv[] UNUSED) +{ + const struct ovsdb_row *row = do_transact_find_row(argv[1]); + ovsdb_txn_row_delete(do_transact_txn, row); +} + +static void +do_transact_modify(int argc UNUSED, char *argv[] UNUSED) +{ + const struct ovsdb_row *row_ro; + struct ovsdb_row *row_rw; + + row_ro = do_transact_find_row(argv[1]); + row_rw = ovsdb_txn_row_modify(do_transact_txn, row_ro); + do_transact_set_i_j(row_rw, argv[2], argv[3]); +} + +static int +compare_rows_by_uuid(const void *a_, const void *b_) +{ + struct ovsdb_row *const *ap = a_; + struct ovsdb_row *const *bp = b_; + + return uuid_compare_3way(ovsdb_row_get_uuid(*ap), ovsdb_row_get_uuid(*bp)); +} + +static void +do_transact_print(int argc UNUSED, char *argv[] UNUSED) +{ + const struct ovsdb_row **rows; + const struct ovsdb_row *row; + size_t n_rows; + size_t i; + + n_rows = hmap_count(&do_transact_table->rows); + rows = xmalloc(n_rows * sizeof *rows); + i = 0; + HMAP_FOR_EACH (row, struct ovsdb_row, hmap_node, + &do_transact_table->rows) { + rows[i++] = row; + } + assert(i == n_rows); + + qsort(rows, n_rows, sizeof *rows, compare_rows_by_uuid); + + for (i = 0; i < n_rows; i++) { + printf("\n%"PRId32": i=%d, j=%d", + ovsdb_row_get_uuid(rows[i])->parts[3], + do_transact_get_integer(rows[i], "i"), + do_transact_get_integer(rows[i], "j")); + } + + free(rows); +} + +static void +do_transact(int argc, char *argv[]) +{ + static const struct command do_transact_commands[] = { + { "commit", 0, 0, do_transact_commit }, + { "abort", 0, 0, do_transact_abort }, + { "insert", 2, 3, do_transact_insert }, + { "delete", 1, 1, do_transact_delete }, + { "modify", 2, 3, do_transact_modify }, + { "print", 0, 0, do_transact_print }, + { NULL, 0, 0, NULL }, + }; + + struct ovsdb_schema *schema; + struct json *json; + int i; + + /* Create table. */ + json = parse_json("{\"name\": \"testdb\", " + " \"tables\": " + " {\"mytable\": " + " {\"columns\": " + " {\"i\": {\"type\": \"integer\"}, " + " \"j\": {\"type\": \"integer\"}}}}}"); + check_ovsdb_error(ovsdb_schema_from_json(json, &schema)); + json_destroy(json); + do_transact_db = ovsdb_create(NULL, schema); + do_transact_table = ovsdb_get_table(do_transact_db, "mytable"); + assert(do_transact_table != NULL); + + for (i = 1; i < argc; i++) { + struct json *command; + size_t n_args; + char **args; + int j; + + command = parse_json(argv[i]); + if (command->type != JSON_ARRAY) { + ovs_fatal(0, "transaction %d must be JSON array " + "with at least 1 element", i); + } + + n_args = command->u.array.n; + args = xmalloc((n_args + 1) * sizeof *args); + for (j = 0; j < n_args; j++) { + struct json *s = command->u.array.elems[j]; + if (s->type != JSON_STRING) { + ovs_fatal(0, "transaction %d argument %d must be JSON string", + i, j); + } + args[j] = xstrdup(json_string(s)); + } + args[n_args] = NULL; + + if (!do_transact_txn) { + do_transact_txn = ovsdb_txn_create(do_transact_db); + } + + for (j = 0; j < n_args; j++) { + if (j) { + putchar(' '); + } + fputs(args[j], stdout); + } + fputs(":", stdout); + run_command(n_args, args, do_transact_commands); + putchar('\n'); + + for (j = 0; j < n_args; j++) { + free(args[j]); + } + free(args); + json_destroy(command); + } + ovsdb_txn_abort(do_transact_txn); + ovsdb_destroy(do_transact_db); /* Also destroys 'schema'. */ +} + +static struct command all_commands[] = { + { "file-io", 2, INT_MAX, do_file_io }, + { "parse-atomic-type", 1, 1, do_parse_atomic_type }, + { "parse-type", 1, 1, do_parse_type }, + { "parse-atoms", 2, INT_MAX, do_parse_atoms }, + { "parse-data", 2, INT_MAX, do_parse_data }, + { "sort-atoms", 2, 2, do_sort_atoms }, + { "parse-column", 2, 2, do_parse_column }, + { "parse-table", 2, 2, do_parse_table }, + { "parse-rows", 2, INT_MAX, do_parse_rows }, + { "compare-rows", 2, INT_MAX, do_compare_rows }, + { "parse-conditions", 2, INT_MAX, do_parse_conditions }, + { "evaluate-conditions", 3, 3, do_evaluate_conditions }, + { "query", 3, 3, do_query }, + { "query-distinct", 4, 4, do_query_distinct }, + { "transact", 1, INT_MAX, do_transact }, + { "execute", 2, INT_MAX, do_execute }, + { "trigger", 2, INT_MAX, do_trigger }, + { "help", 0, INT_MAX, do_help }, + { NULL, 0, 0, NULL }, +}; diff --git a/tests/testsuite.at b/tests/testsuite.at index 781b6a6c..9b47b8fb 100644 --- a/tests/testsuite.at +++ b/tests/testsuite.at @@ -16,6 +16,7 @@ limitations under the License.]) AT_TESTED([ovs-vswitchd]) AT_TESTED([ovs-vsctl]) +AT_TESTED([perl]) m4_include([tests/lcov-pre.at]) m4_include([tests/library.at]) @@ -26,6 +27,7 @@ m4_include([tests/json.at]) m4_include([tests/jsonrpc.at]) m4_include([tests/timeval.at]) m4_include([tests/lockfile.at]) +m4_include([tests/ovsdb.at]) m4_include([tests/stp.at]) m4_include([tests/ovs-vsctl.at]) m4_include([tests/lcov-post.at]) diff --git a/tests/uuidfilt.pl b/tests/uuidfilt.pl new file mode 100755 index 00000000..6f003a52 --- /dev/null +++ b/tests/uuidfilt.pl @@ -0,0 +1,21 @@ +#! /usr/bin/perl + +use strict; +use warnings; + +our %uuids; +our $n_uuids = 0; +sub lookup_uuid { + my ($uuid) = @_; + if (!exists($uuids{$uuid})) { + $uuids{$uuid} = $n_uuids++; + } + return "<$uuids{$uuid}>"; +} + +my $u = '[0-9a-fA-F]'; +my $uuid_re = "${u}{8}-${u}{4}-${u}{4}-${u}{4}-${u}{12}"; +while (<>) { + s/($uuid_re)/lookup_uuid($1)/eg; + print $_; +} -- 2.30.2