From: Ben Pfaff Date: Wed, 2 Dec 2009 19:26:15 +0000 (-0800) Subject: ovsdb: Implement C bindings for IDL. X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c3bb4bd7f1d9c045a5e5d7062b09d4dac4e48195;p=openvswitch ovsdb: Implement C bindings for IDL. --- diff --git a/Makefile.am b/Makefile.am index 4229cb1d..301fc7c1 100644 --- a/Makefile.am +++ b/Makefile.am @@ -41,16 +41,18 @@ dist_man_MANS = dist_pkgdata_SCRIPTS = dist_sbin_SCRIPTS = man_MANS = +noinst_DATA = noinst_HEADERS = noinst_LIBRARIES = noinst_PROGRAMS = noinst_SCRIPTS = +SUFFIXES = EXTRA_DIST += soexpand.pl ro_c = echo '/* -*- mode: c; buffer-read-only: t -*- */' -SUFFIXES = .in +SUFFIXES += .in .in: $(PERL) $(srcdir)/soexpand.pl -I$(srcdir) < $< | \ sed \ diff --git a/lib/automake.mk b/lib/automake.mk index 406b4240..ee5a1956 100644 --- a/lib/automake.mk +++ b/lib/automake.mk @@ -81,6 +81,9 @@ lib_libopenvswitch_a_SOURCES = \ lib/ovsdb-data.h \ lib/ovsdb-error.c \ lib/ovsdb-error.h \ + lib/ovsdb-idl-provider.h \ + lib/ovsdb-idl.c \ + lib/ovsdb-idl.h \ lib/ovsdb-parser.c \ lib/ovsdb-parser.h \ lib/ovsdb-types.c \ diff --git a/lib/ovsdb-idl-provider.h b/lib/ovsdb-idl-provider.h new file mode 100644 index 00000000..76197e8c --- /dev/null +++ b/lib/ovsdb-idl-provider.h @@ -0,0 +1,71 @@ +/* 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_IDL_PROVIDER_H +#define OVSDB_IDL_PROVIDER_H 1 + +#include "hmap.h" +#include "list.h" +#include "ovsdb-idl.h" +#include "ovsdb-types.h" +#include "shash.h" +#include "uuid.h" + +struct ovsdb_idl_row { + struct hmap_node hmap_node; /* In struct ovsdb_idl_table's 'rows'. */ + struct uuid uuid; /* Row "_uuid" field. */ + struct list src_arcs; /* Forward arcs (ovsdb_idl_arc.src_node). */ + struct list dst_arcs; /* Backward arcs (ovsdb_idl_arc.dst_node). */ + struct ovsdb_idl_table *table; /* Containing table. */ + struct ovsdb_datum *fields; /* Row data, or null if orphaned. */ +}; + +struct ovsdb_idl_column { + char *name; + struct ovsdb_type type; +}; + +struct ovsdb_idl_table_class { + char *name; + const struct ovsdb_idl_column *columns; + size_t n_columns; + size_t allocation_size; + void (*parse)(struct ovsdb_idl_row *); + void (*unparse)(struct ovsdb_idl_row *); +}; + +struct ovsdb_idl_table { + const struct ovsdb_idl_table_class *class; + struct shash columns; /* Contains "const struct ovsdb_idl_column *"s. */ + struct hmap rows; /* Contains "struct ovsdb_idl_row"s. */ + struct ovsdb_idl *idl; /* Containing idl. */ +}; + +struct ovsdb_idl_class { + const struct ovsdb_idl_table_class *tables; + size_t n_tables; +}; + +struct ovsdb_idl_row *ovsdb_idl_get_row_arc( + struct ovsdb_idl_row *src, + struct ovsdb_idl_table_class *dst_table, + const struct uuid *dst_uuid); + +struct ovsdb_idl_row *ovsdb_idl_first_row( + const struct ovsdb_idl *, const struct ovsdb_idl_table_class *); + +struct ovsdb_idl_row *ovsdb_idl_next_row(const struct ovsdb_idl_row *); + +#endif /* ovsdb-idl-provider.h */ diff --git a/lib/ovsdb-idl.c b/lib/ovsdb-idl.c new file mode 100644 index 00000000..26b5942d --- /dev/null +++ b/lib/ovsdb-idl.c @@ -0,0 +1,691 @@ +/* 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-idl.h" + +#include +#include +#include + +#include "json.h" +#include "jsonrpc.h" +#include "ovsdb-data.h" +#include "ovsdb-error.h" +#include "ovsdb-idl-provider.h" +#include "shash.h" +#include "util.h" + +#define THIS_MODULE VLM_ovsdb_idl +#include "vlog.h" + +/* An arc from one idl_row to another. When row A contains a UUID that + * references row B, this is represented by an arc from A (the source) to B + * (the destination). + * + * Arcs from a row to itself are omitted, that is, src and dst are always + * different. + * + * Arcs are never duplicated, that is, even if there are multiple references + * from A to B, there is only a single arc from A to B. + * + * Arcs are directed: an arc from A to B is the converse of an an arc from B to + * A. Both an arc and its converse may both be present, if each row refers + * to the other circularly. + * + * The source and destination row may be in the same table or in different + * tables. + */ +struct ovsdb_idl_arc { + struct list src_node; /* In src->src_arcs list. */ + struct list dst_node; /* In dst->dst_arcs list. */ + struct ovsdb_idl_row *src; /* Source row. */ + struct ovsdb_idl_row *dst; /* Destination row. */ +}; + +struct ovsdb_idl { + struct jsonrpc_session *session; + struct shash tables; + struct json *monitor_request_id; + unsigned int last_monitor_request_seqno; + unsigned int change_seqno; +}; + +static struct vlog_rate_limit syntax_rl = VLOG_RATE_LIMIT_INIT(1, 5); +static struct vlog_rate_limit semantic_rl = VLOG_RATE_LIMIT_INIT(1, 5); + +static void ovsdb_idl_clear(struct ovsdb_idl *); +static void ovsdb_idl_send_monitor_request(struct ovsdb_idl *); +static void ovsdb_idl_parse_update(struct ovsdb_idl *, const struct json *); +static struct ovsdb_error *ovsdb_idl_parse_update__(struct ovsdb_idl *, + const struct json *); +static void ovsdb_idl_process_update(struct ovsdb_idl_table *, + const struct uuid *, + const struct json *old, + const struct json *new); +static void ovsdb_idl_insert_row(struct ovsdb_idl_row *, const struct json *); +static void ovsdb_idl_delete_row(struct ovsdb_idl_row *); +static void ovsdb_idl_modify_row(struct ovsdb_idl_row *, const struct json *); + +static bool ovsdb_idl_row_is_orphan(const struct ovsdb_idl_row *); +static struct ovsdb_idl_row *ovsdb_idl_row_create(struct ovsdb_idl_table *, + const struct uuid *); +static void ovsdb_idl_row_destroy(struct ovsdb_idl_row *); + +static void ovsdb_idl_row_clear_fields(struct ovsdb_idl_row *); + +struct ovsdb_idl * +ovsdb_idl_create(const char *remote, const struct ovsdb_idl_class *class) +{ + struct ovsdb_idl *idl; + size_t i; + + idl = xzalloc(sizeof *idl); + idl->session = jsonrpc_session_open(remote); + shash_init(&idl->tables); + for (i = 0; i < class->n_tables; i++) { + const struct ovsdb_idl_table_class *tc = &class->tables[i]; + struct ovsdb_idl_table *table; + size_t j; + + table = xmalloc(sizeof *table); + assert(!shash_find(&idl->tables, tc->name)); + shash_add(&idl->tables, tc->name, table); + table->class = tc; + shash_init(&table->columns); + for (j = 0; j < tc->n_columns; j++) { + const struct ovsdb_idl_column *column = &tc->columns[j]; + + assert(!shash_find(&table->columns, column->name)); + shash_add(&table->columns, column->name, column); + } + hmap_init(&table->rows); + table->idl = idl; + } + idl->last_monitor_request_seqno = UINT_MAX; + + return idl; +} + +void +ovsdb_idl_destroy(struct ovsdb_idl *idl) +{ + if (idl) { + struct shash_node *node; + + ovsdb_idl_clear(idl); + jsonrpc_session_close(idl->session); + + SHASH_FOR_EACH (node, &idl->tables) { + struct ovsdb_idl_table *table = node->data; + + shash_destroy(&table->columns); + hmap_destroy(&table->rows); + } + shash_destroy(&idl->tables); + json_destroy(idl->monitor_request_id); + free(idl); + } +} + +static void +ovsdb_idl_clear(struct ovsdb_idl *idl) +{ + struct shash_node *node; + bool changed = false; + + SHASH_FOR_EACH (node, &idl->tables) { + struct ovsdb_idl_table *table = node->data; + struct ovsdb_idl_row *row, *next_row; + + if (hmap_is_empty(&table->rows)) { + continue; + } + + changed = true; + HMAP_FOR_EACH_SAFE (row, next_row, struct ovsdb_idl_row, hmap_node, + &table->rows) { + struct ovsdb_idl_arc *arc, *next_arc; + + if (!ovsdb_idl_row_is_orphan(row)) { + (row->table->class->unparse)(row); + ovsdb_idl_row_clear_fields(row); + } + hmap_remove(&table->rows, &row->hmap_node); + LIST_FOR_EACH_SAFE (arc, next_arc, struct ovsdb_idl_arc, src_node, + &row->src_arcs) { + free(arc); + } + /* No need to do anything with dst_arcs: some node has those arcs + * as forward arcs and will destroy them itself. */ + + free(row); + } + } + + if (changed) { + idl->change_seqno++; + } +} + +void +ovsdb_idl_run(struct ovsdb_idl *idl) +{ + int i; + + jsonrpc_session_run(idl->session); + for (i = 0; jsonrpc_session_is_connected(idl->session) && i < 50; i++) { + struct jsonrpc_msg *msg, *reply; + unsigned int seqno; + + seqno = jsonrpc_session_get_seqno(idl->session); + if (idl->last_monitor_request_seqno != seqno) { + idl->last_monitor_request_seqno = seqno; + ovsdb_idl_send_monitor_request(idl); + break; + } + + msg = jsonrpc_session_recv(idl->session); + if (!msg) { + break; + } + + reply = NULL; + if (msg->type == JSONRPC_REQUEST && !strcmp(msg->method, "echo")) { + reply = jsonrpc_create_reply(json_clone(msg->params), msg->id); + } else if (msg->type == JSONRPC_NOTIFY + && !strcmp(msg->method, "update") + && msg->params->type == JSON_ARRAY + && msg->params->u.array.n == 2 + && msg->params->u.array.elems[0]->type == JSON_NULL) { + ovsdb_idl_parse_update(idl, msg->params->u.array.elems[1]); + } else if (msg->type == JSONRPC_REPLY + && idl->monitor_request_id + && json_equal(idl->monitor_request_id, msg->id)) { + json_destroy(idl->monitor_request_id); + idl->monitor_request_id = NULL; + ovsdb_idl_clear(idl); + ovsdb_idl_parse_update(idl, msg->result); + } else if (msg->type == JSONRPC_REPLY + && msg->id && msg->id->type == JSON_STRING + && !strcmp(msg->id->u.string, "echo")) { + /* It's a reply to our echo request. Ignore it. */ + } else { + VLOG_WARN("%s: received unexpected %s message", + jsonrpc_session_get_name(idl->session), + jsonrpc_msg_type_to_string(msg->type)); + jsonrpc_session_force_reconnect(idl->session); + } + if (reply) { + jsonrpc_session_send(idl->session, reply); + } + jsonrpc_msg_destroy(msg); + } +} + +void +ovsdb_idl_wait(struct ovsdb_idl *idl) +{ + jsonrpc_session_wait(idl->session); + jsonrpc_session_recv_wait(idl->session); +} + +unsigned int +ovsdb_idl_get_seqno(const struct ovsdb_idl *idl) +{ + return idl->change_seqno; +} + +void +ovsdb_idl_force_reconnect(struct ovsdb_idl *idl) +{ + jsonrpc_session_force_reconnect(idl->session); +} + +static void +ovsdb_idl_send_monitor_request(struct ovsdb_idl *idl) +{ + struct json *monitor_requests; + const struct shash_node *node; + struct jsonrpc_msg *msg; + + monitor_requests = json_object_create(); + SHASH_FOR_EACH (node, &idl->tables) { + const struct ovsdb_idl_table *table = node->data; + const struct ovsdb_idl_table_class *tc = table->class; + struct json *monitor_request, *columns; + size_t i; + + monitor_request = json_object_create(); + columns = json_array_create_empty(); + for (i = 0; i < tc->n_columns; i++) { + const struct ovsdb_idl_column *column = &tc->columns[i]; + json_array_add(columns, json_string_create(column->name)); + } + json_object_put(monitor_request, "columns", columns); + json_object_put(monitor_requests, tc->name, monitor_request); + } + + json_destroy(idl->monitor_request_id); + msg = jsonrpc_create_request( + "monitor", json_array_create_2(json_null_create(), monitor_requests), + &idl->monitor_request_id); + jsonrpc_session_send(idl->session, msg); +} + +static void +ovsdb_idl_parse_update(struct ovsdb_idl *idl, const struct json *table_updates) +{ + struct ovsdb_error *error; + + idl->change_seqno++; + + error = ovsdb_idl_parse_update__(idl, table_updates); + if (error) { + if (!VLOG_DROP_WARN(&syntax_rl)) { + char *s = ovsdb_error_to_string(error); + VLOG_WARN_RL(&syntax_rl, "%s", s); + free(s); + } + ovsdb_error_destroy(error); + } +} + +static struct ovsdb_error * +ovsdb_idl_parse_update__(struct ovsdb_idl *idl, + const struct json *table_updates) +{ + const struct shash_node *tables_node; + + if (table_updates->type != JSON_OBJECT) { + return ovsdb_syntax_error(table_updates, NULL, + " is not an object"); + } + SHASH_FOR_EACH (tables_node, json_object(table_updates)) { + const struct json *table_update = tables_node->data; + const struct shash_node *table_node; + struct ovsdb_idl_table *table; + + table = shash_find_data(&idl->tables, tables_node->name); + if (!table) { + return ovsdb_syntax_error( + table_updates, NULL, + " includes unknown table \"%s\"", + tables_node->name); + } + + if (table_update->type != JSON_OBJECT) { + return ovsdb_syntax_error(table_update, NULL, + " for table \"%s\" is " + "not an object", table->class->name); + } + SHASH_FOR_EACH (table_node, json_object(table_update)) { + const struct json *row_update = table_node->data; + const struct json *old_json, *new_json; + struct uuid uuid; + + if (!uuid_from_string(&uuid, table_node->name)) { + return ovsdb_syntax_error(table_update, NULL, + " for table \"%s\" " + "contains bad UUID " + "\"%s\" as member name", + table->class->name, + table_node->name); + } + if (row_update->type != JSON_OBJECT) { + return ovsdb_syntax_error(row_update, NULL, + " for table \"%s\" " + "contains for %s that " + "is not an object", + table->class->name, + table_node->name); + } + + old_json = shash_find_data(json_object(row_update), "old"); + new_json = shash_find_data(json_object(row_update), "new"); + if (old_json && old_json->type != JSON_OBJECT) { + return ovsdb_syntax_error(old_json, NULL, + "\"old\" is not object"); + } else if (new_json && new_json->type != JSON_OBJECT) { + return ovsdb_syntax_error(new_json, NULL, + "\"new\" is not object"); + } else if ((old_json != NULL) + (new_json != NULL) + != shash_count(json_object(row_update))) { + return ovsdb_syntax_error(row_update, NULL, + " contains unexpected " + "member"); + } else if (!old_json && !new_json) { + return ovsdb_syntax_error(row_update, NULL, + " missing \"old\" " + "and \"new\" members"); + } + + ovsdb_idl_process_update(table, &uuid, old_json, new_json); + } + } + + return NULL; +} + +static struct ovsdb_idl_row * +ovsdb_idl_get_row(struct ovsdb_idl_table *table, const struct uuid *uuid) +{ + struct ovsdb_idl_row *row; + + HMAP_FOR_EACH_WITH_HASH (row, struct ovsdb_idl_row, hmap_node, + uuid_hash(uuid), &table->rows) { + if (uuid_equals(&row->uuid, uuid)) { + return row; + } + } + return NULL; +} + +static void +ovsdb_idl_process_update(struct ovsdb_idl_table *table, + const struct uuid *uuid, const struct json *old, + const struct json *new) +{ + struct ovsdb_idl_row *row; + + row = ovsdb_idl_get_row(table, uuid); + if (!new) { + /* Delete row. */ + if (row && !ovsdb_idl_row_is_orphan(row)) { + /* XXX perhaps we should check the 'old' values? */ + ovsdb_idl_delete_row(row); + } else { + VLOG_WARN_RL(&semantic_rl, "cannot delete missing row "UUID_FMT" " + "from table %s", + UUID_ARGS(uuid), table->class->name); + } + } else if (!old) { + /* Insert row. */ + if (!row) { + ovsdb_idl_insert_row(ovsdb_idl_row_create(table, uuid), new); + } else if (ovsdb_idl_row_is_orphan(row)) { + ovsdb_idl_insert_row(row, new); + } else { + VLOG_WARN_RL(&semantic_rl, "cannot add existing row "UUID_FMT" to " + "table %s", UUID_ARGS(uuid), table->class->name); + ovsdb_idl_modify_row(row, new); + } + } else { + /* Modify row. */ + if (row) { + /* XXX perhaps we should check the 'old' values? */ + if (!ovsdb_idl_row_is_orphan(row)) { + ovsdb_idl_modify_row(row, new); + } else { + VLOG_WARN_RL(&semantic_rl, "cannot modify missing but " + "referenced row "UUID_FMT" in table %s", + UUID_ARGS(uuid), table->class->name); + ovsdb_idl_insert_row(row, new); + } + } else { + VLOG_WARN_RL(&semantic_rl, "cannot modify missing row "UUID_FMT" " + "in table %s", UUID_ARGS(uuid), table->class->name); + ovsdb_idl_insert_row(ovsdb_idl_row_create(table, uuid), new); + } + } +} + +static void +ovsdb_idl_row_update(struct ovsdb_idl_row *row, const struct json *row_json) +{ + struct ovsdb_idl_table *table = row->table; + struct shash_node *node; + + SHASH_FOR_EACH (node, json_object(row_json)) { + const char *column_name = node->name; + const struct ovsdb_idl_column *column; + struct ovsdb_datum datum; + struct ovsdb_error *error; + + column = shash_find_data(&table->columns, column_name); + if (!column) { + VLOG_WARN_RL(&syntax_rl, "unknown column %s updating row "UUID_FMT, + column_name, UUID_ARGS(&row->uuid)); + continue; + } + + error = ovsdb_datum_from_json(&datum, &column->type, node->data, NULL); + if (!error) { + ovsdb_datum_swap(&row->fields[column - table->class->columns], + &datum); + ovsdb_datum_destroy(&datum, &column->type); + } else { + char *s = ovsdb_error_to_string(error); + VLOG_WARN_RL(&syntax_rl, "error parsing column %s in row "UUID_FMT + " in table %s: %s", column_name, + UUID_ARGS(&row->uuid), table->class->name, s); + free(s); + ovsdb_error_destroy(error); + } + } +} + +static bool +ovsdb_idl_row_is_orphan(const struct ovsdb_idl_row *row) +{ + return !row->fields; +} + +static void +ovsdb_idl_row_clear_fields(struct ovsdb_idl_row *row) +{ + if (!ovsdb_idl_row_is_orphan(row)) { + const struct ovsdb_idl_table_class *class = row->table->class; + size_t i; + + for (i = 0; i < class->n_columns; i++) { + ovsdb_datum_destroy(&row->fields[i], &class->columns[i].type); + } + row->fields = NULL; + } +} + +static void +ovsdb_idl_row_clear_arcs(struct ovsdb_idl_row *row, bool destroy_dsts) +{ + struct ovsdb_idl_arc *arc, *next; + + /* Delete all forward arcs. If 'destroy_dsts', destroy any orphaned rows + * that this causes to be unreferenced. */ + LIST_FOR_EACH_SAFE (arc, next, struct ovsdb_idl_arc, src_node, + &row->src_arcs) { + list_remove(&arc->dst_node); + if (destroy_dsts + && ovsdb_idl_row_is_orphan(arc->dst) + && list_is_empty(&arc->dst->dst_arcs)) { + ovsdb_idl_row_destroy(arc->dst); + } + free(arc); + } + list_init(&row->src_arcs); +} + +/* Force nodes that reference 'row' to reparse. */ +static void +ovsdb_idl_row_reparse_backrefs(struct ovsdb_idl_row *row, bool destroy_dsts) +{ + struct ovsdb_idl_arc *arc, *next; + + /* This is trickier than it looks. ovsdb_idl_row_clear_arcs() will destroy + * 'arc', so we need to use the "safe" variant of list traversal. However, + * calling ref->table->class->parse will add an arc equivalent to 'arc' to + * row->arcs. That could be a problem for traversal, but it adds it at the + * beginning of the list to prevent us from stumbling upon it again. + * + * (If duplicate arcs were possible then we would need to make sure that + * 'next' didn't also point into 'arc''s destination, but we forbid + * duplicate arcs.) */ + LIST_FOR_EACH_SAFE (arc, next, struct ovsdb_idl_arc, dst_node, + &row->dst_arcs) { + struct ovsdb_idl_row *ref = arc->src; + + (ref->table->class->unparse)(ref); + ovsdb_idl_row_clear_arcs(ref, destroy_dsts); + (ref->table->class->parse)(ref); + } +} + +static struct ovsdb_idl_row * +ovsdb_idl_row_create(struct ovsdb_idl_table *table, const struct uuid *uuid) +{ + struct ovsdb_idl_row *row = xmalloc(table->class->allocation_size); + hmap_insert(&table->rows, &row->hmap_node, uuid_hash(uuid)); + row->uuid = *uuid; + list_init(&row->src_arcs); + list_init(&row->dst_arcs); + row->table = table; + row->fields = NULL; + return row; +} + +static void +ovsdb_idl_row_destroy(struct ovsdb_idl_row *row) +{ + if (row) { + ovsdb_idl_row_clear_fields(row); + hmap_remove(&row->table->rows, &row->hmap_node); + free(row); + } +} + +static void +ovsdb_idl_insert_row(struct ovsdb_idl_row *row, const struct json *row_json) +{ + const struct ovsdb_idl_table_class *class = row->table->class; + size_t i; + + assert(!row->fields); + row->fields = xmalloc(class->n_columns * sizeof *row->fields); + for (i = 0; i < class->n_columns; i++) { + ovsdb_datum_init_default(&row->fields[i], &class->columns[i].type); + } + ovsdb_idl_row_update(row, row_json); + (class->parse)(row); + + ovsdb_idl_row_reparse_backrefs(row, false); +} + +static void +ovsdb_idl_delete_row(struct ovsdb_idl_row *row) +{ + (row->table->class->unparse)(row); + ovsdb_idl_row_clear_arcs(row, true); + ovsdb_idl_row_clear_fields(row); + if (list_is_empty(&row->dst_arcs)) { + ovsdb_idl_row_destroy(row); + } else { + ovsdb_idl_row_reparse_backrefs(row, true); + } +} + +static void +ovsdb_idl_modify_row(struct ovsdb_idl_row *row, const struct json *row_json) +{ + (row->table->class->unparse)(row); + ovsdb_idl_row_clear_arcs(row, true); + ovsdb_idl_row_update(row, row_json); + (row->table->class->parse)(row); +} + +static bool +may_add_arc(const struct ovsdb_idl_row *src, const struct ovsdb_idl_row *dst) +{ + const struct ovsdb_idl_arc *arc; + + /* No self-arcs. */ + if (src == dst) { + return false; + } + + /* No duplicate arcs. + * + * We only need to test whether the first arc in dst->dst_arcs originates + * at 'src', since we add all of the arcs from a given source in a clump + * (in a single call to a row's ->parse function) and new arcs are always + * added at the front of the dst_arcs list. */ + if (list_is_empty(&dst->dst_arcs)) { + return true; + } + arc = CONTAINER_OF(dst->dst_arcs.next, struct ovsdb_idl_arc, dst_node); + return arc->src != src; +} + +struct ovsdb_idl_row * +ovsdb_idl_get_row_arc(struct ovsdb_idl_row *src, + struct ovsdb_idl_table_class *dst_table_class, + const struct uuid *dst_uuid) +{ + struct ovsdb_idl *idl = src->table->idl; + struct ovsdb_idl_arc *arc; + struct ovsdb_idl_row *dst; + + dst = ovsdb_idl_get_row(src->table, dst_uuid); + if (!dst) { + struct ovsdb_idl_table *dst_table; + dst_table = shash_find_data(&idl->tables, dst_table_class->name); + dst = ovsdb_idl_row_create(dst_table, dst_uuid); + } + + /* Add a new arc, if it wouldn't be a self-arc or a duplicate arc. */ + if (may_add_arc(src, dst)) { + /* The arc *must* be added at the front of the dst_arcs list. See + * ovsdb_idl_row_reparse_backrefs() for details. */ + arc = xmalloc(sizeof *arc); + list_push_front(&src->src_arcs, &arc->src_node); + list_push_front(&dst->dst_arcs, &arc->dst_node); + arc->src = src; + arc->dst = dst; + } + + return !ovsdb_idl_row_is_orphan(dst) ? dst : NULL; +} + +static struct ovsdb_idl_row * +next_real_row(struct ovsdb_idl_table *table, struct hmap_node *node) +{ + for (; node; node = hmap_next(&table->rows, node)) { + struct ovsdb_idl_row *row; + + row = CONTAINER_OF(node, struct ovsdb_idl_row, hmap_node); + if (!ovsdb_idl_row_is_orphan(row)) { + return row; + } + } + return NULL; +} + +struct ovsdb_idl_row * +ovsdb_idl_first_row(const struct ovsdb_idl *idl, + const struct ovsdb_idl_table_class *table_class) +{ + struct ovsdb_idl_table *table; + + table = shash_find_data(&idl->tables, table_class->name); + return next_real_row(table, hmap_first(&table->rows)); +} + +struct ovsdb_idl_row * +ovsdb_idl_next_row(const struct ovsdb_idl_row *row) +{ + struct ovsdb_idl_table *table = row->table; + + return next_real_row(table, hmap_next(&table->rows, &row->hmap_node)); +} diff --git a/lib/ovsdb-idl.h b/lib/ovsdb-idl.h new file mode 100644 index 00000000..7e95bb10 --- /dev/null +++ b/lib/ovsdb-idl.h @@ -0,0 +1,31 @@ +/* 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_IDL_H +#define OVSDB_IDL_H 1 + +struct ovsdb_idl_class; + +struct ovsdb_idl *ovsdb_idl_create(const char *remote, + const struct ovsdb_idl_class *); +void ovsdb_idl_destroy(struct ovsdb_idl *); + +void ovsdb_idl_run(struct ovsdb_idl *); +void ovsdb_idl_wait(struct ovsdb_idl *); + +unsigned int ovsdb_idl_get_seqno(const struct ovsdb_idl *); +void ovsdb_idl_force_reconnect(struct ovsdb_idl *); + +#endif /* ovsdb-idl.h */ diff --git a/ovsdb/automake.mk b/ovsdb/automake.mk index f3d4c976..2dcc4638 100644 --- a/ovsdb/automake.mk +++ b/ovsdb/automake.mk @@ -82,3 +82,13 @@ EXTRA_DIST += \ ovsdb/ovsdb-idlc.in \ ovsdb/ovsdb-idlc.1 DISTCLEANFILES += ovsdb/ovsdb-idlc +SUFFIXES += .ovsidl +.ovsidl.c: + $(PYTHON) $(srcdir)/ovsdb/ovsdb-idlc.in c-idl-source $< > $@.tmp + mv $@.tmp $@ +.ovsidl.h: + $(PYTHON) $(srcdir)/ovsdb/ovsdb-idlc.in c-idl-header $< > $@.tmp + mv $@.tmp $@ +.ovsidl.ovsschema: + $(PYTHON) $(srcdir)/ovsdb/ovsdb-idlc.in ovsdb-schema $< > $@.tmp + mv $@.tmp $@ diff --git a/ovsdb/ovsdb-idlc.1 b/ovsdb/ovsdb-idlc.1 index ee27730a..1311759c 100644 --- a/ovsdb/ovsdb-idlc.1 +++ b/ovsdb/ovsdb-idlc.1 @@ -34,6 +34,17 @@ with a few additions: Lines that begin with \fB//\fR (two forward slashes) are ignored and thus can be used for comments. . +.IP "\fB""\fBidlPrefix\fR"" member of " +This member, which is required, specifies a string that is prefixed to +top-level names in C bindings. It should probably end in an +underscore. +. +.IP "\fB""\fBidlHeader\fR"" member of " +This member, which is required, specifies the name of the IDL header. +It will be output on an \fB#include\fR line in the source file +generated by the C bindings. It should include the bracketing +\fB""\fR or \fB<>\fR. +. .IP "\fB""\fBkeyRefTable\fR"" member of " A whose \fBkey\fR is \fB"uuid"\fR may have an additional member named \fB"keyRefTable"\fR, whose value is a table name. This diff --git a/ovsdb/ovsdb-idlc.in b/ovsdb/ovsdb-idlc.in index 681cba5d..68cb4ce6 100755 --- a/ovsdb/ovsdb-idlc.in +++ b/ovsdb/ovsdb-idlc.in @@ -30,10 +30,12 @@ def mustGetMember(json, name, expectedType, description): return member class DbSchema: - def __init__(self, name, comment, tables): + def __init__(self, name, comment, tables, idlPrefix, idlHeader): self.name = name self.comment = comment self.tables = tables + self.idlPrefix = idlPrefix + self.idlHeader = idlHeader @staticmethod def fromJson(json): @@ -41,9 +43,11 @@ class DbSchema: comment = getMember(json, 'comment', [unicode], 'database') tablesJson = mustGetMember(json, 'tables', [dict], 'database') tables = {} - for name, json in tablesJson.iteritems(): - tables[name] = TableSchema.fromJson(json, "%s table" % name) - return DbSchema(name, comment, tables) + for name, tableJson in tablesJson.iteritems(): + tables[name] = TableSchema.fromJson(tableJson, "%s table" % name) + idlPrefix = mustGetMember(json, 'idlPrefix', [unicode], 'database') + idlHeader = mustGetMember(json, 'idlHeader', [unicode], 'database') + return DbSchema(name, comment, tables, idlPrefix, idlHeader) def toJson(self): d = {"name": self.name, @@ -156,7 +160,7 @@ def cBaseType(prefix, type, refTable=None): 'string': 'char *'}[type] def printCIDLHeader(schema): - prefix = 'ovsrec_' + prefix = schema.idlPrefix print '''\ /* Generated automatically -- do not modify! -*- buffer-read-only: t -*- */ @@ -166,6 +170,7 @@ def printCIDLHeader(schema): #include #include #include +#include "ovsdb-idl-provider.h" #include "uuid.h"''' % {'prefix': prefix.upper()} for tableName, table in schema.tables.iteritems(): print @@ -173,10 +178,9 @@ def printCIDLHeader(schema): print "/* %s table (%s). */" % (tableName, table.comment) else: print "/* %s table. */" % (tableName) - print "struct %s%s {" % (prefix, tableName.lower()) - print "\t/* Columns automatically included in every table. */" - print "\tstruct uuid uuid_;" - print "\tstruct uuid version_;" + structName = "%s%s" % (prefix, tableName.lower()) + print "struct %s {" % structName + print "\tstruct ovsdb_idl_row header_;" for columnName, column in table.columns.iteritems(): print "\n\t/* %s column. */" % columnName type = column.type @@ -193,9 +197,205 @@ def printCIDLHeader(schema): print "\t%s%s%s;" % (cBaseType(prefix, type.key, type.keyRefTable), pointer, columnName) if not singleton: print "\tsize_t n_%s;" % columnName + print ''' +}; + +const struct %(s)s *%(s)s_first(const struct ovsdb_idl *); +const struct %(s)s *%(s)s_next(const struct %(s)s *); +#define %(S)s_FOR_EACH(ROW, IDL) for ((ROW) = %(s)s_first(IDL); (ROW); (ROW) = %(s)s_next(ROW))''' % {'s': structName, 'S': structName.upper()} + print "\nextern struct ovsdb_idl_class %sidl_class;" % prefix + print "\n#endif /* %(prefix)sIDL_HEADER */" % {'prefix': prefix.upper()} + +def printEnum(members): + if len(members) == 0: + return + + print "\nenum {"; + for member in members[:-1]: + print " %s," % member + print " %s" % members[-1] + print "};" + +def printCIDLSource(schema): + prefix = schema.idlPrefix + print '''\ +/* Generated automatically -- do not modify! -*- buffer-read-only: t -*- */ + +#include +#include %s +#include +#include "ovsdb-data.h"''' % schema.idlHeader + + # Table indexes. + printEnum(["%sTABLE_%s" % (prefix.upper(), tableName.upper()) for tableName in schema.tables] + ["%sN_TABLES" % prefix.upper()]) + print "\nstatic struct ovsdb_idl_table_class %stable_classes[%sN_TABLES];" % (prefix, prefix.upper()) + + for tableName, table in schema.tables.iteritems(): + structName = "%s%s" % (prefix, tableName.lower()) + print " " + if table.comment != None: + print "/* %s table (%s). */" % (tableName, table.comment) + else: + print "/* %s table. */" % (tableName) + + # Column indexes. + printEnum(["%s_COL_%s" % (structName.upper(), columnName.upper()) + for columnName in table.columns] + + ["%s_N_COLUMNS" % structName.upper()]) + + # Parse function. + print ''' +static void +%s_parse(struct ovsdb_idl_row *row_) +{ + struct %s *row = (struct %s *) row_; + const struct ovsdb_datum *datum; + size_t i UNUSED; + + memset(row_ + 1, 0, sizeof *row - sizeof *row_);''' % (structName, structName, structName) + + + for columnName, column in table.columns.iteritems(): + type = column.type + refKey = type.key == "uuid" and type.keyRefTable + refValue = type.value == "uuid" and type.valueRefTable + print + print " datum = &row_->fields[%s_COL_%s];" % (structName.upper(), columnName.upper()) + if type.value: + keyVar = "row->key_%s" % columnName + valueVar = "row->value_%s" % columnName + else: + keyVar = "row->%s" % columnName + valueVar = None + + if type.min == 1 and type.max == 1: + print " if (datum->n >= 1) {" + if not refKey: + print " %s = datum->keys[0].%s;" % (keyVar, type.key) + else: + print " %s = (struct %s%s *) ovsdb_idl_get_row_arc(row_, &%stable_classes[%sTABLE_%s], &datum->keys[0].uuid);" % (keyVar, prefix, type.keyRefTable.lower(), prefix, prefix.upper(), type.keyRefTable.upper()) + + if valueVar: + if refValue: + print " %s = datum->values[0].%s;" % (valueVar, type.value) + else: + print " %s = (struct %s%s *) ovsdb_idl_get_row_arc(row_, &%stable_classes[%sTABLE_%s], &datum->values[0].uuid);" % (valueVar, prefix, type.valueRefTable.lower(), prefix, prefix.upper(), type.valueRefTable.upper()) + print " }" + else: + if type.max != 'unlimited': + nMax = "MIN(%d, datum->n)" % type.max + else: + nMax = "datum->n" + print " for (i = 0; i < %s; i++) {" % nMax + refs = [] + if refKey: + print " struct %s%s *keyRow = (struct %s%s *) ovsdb_idl_get_row_arc(row_, &%stable_classes[%sTABLE_%s], &datum->keys[i].uuid);" % (prefix, type.keyRefTable.lower(), prefix, type.keyRefTable.lower(), prefix, prefix.upper(), type.keyRefTable.upper()) + keySrc = "keyRow" + refs.append('keyRow') + else: + keySrc = "datum->keys[i].%s" % type.key + if refValue: + print " struct %s%s *valueRow = (struct %s%s *) ovsdb_idl_get_row_arc(row_, &%stable_classes[%sTABLE_%s], &datum->values[i].uuid);" % (prefix, type.valueRefTable.lower(), prefix, type.valueRefTable.lower(), prefix, prefix.upper(), type.valueRefTable.upper()) + valueSrc = "valueRow" + refs.append('valueRow') + elif valueVar: + valueSrc = "datum->values[i].%s" % type.value + if refs: + print " if (%s) {" % ' && '.join(refs) + indent = " " + else: + indent = " " + print "%sif (!row->n_%s) {" % (indent, columnName) + print "%s %s = xmalloc(%s * sizeof *%s);" % (indent, keyVar, nMax, keyVar) + if valueVar: + print "%s %s = xmalloc(%s * sizeof %%s);" % (indent, valueVar, nMax, valueVar) + print "%s}" % indent + print "%s%s[row->n_%s] = %s;" % (indent, keyVar, columnName, keySrc) + if valueVar: + print "%s[row->n_%s] = %s;" % (indent, valueVar, columnName, valueSrc) + print "%srow->n_%s++;" % (indent, columnName) + if refs: + print " }" + print " }" + print "}" + + # Unparse function. + nArrays = 0 + for columnName, column in table.columns.iteritems(): + type = column.type + if type.min != 1 or type.max != 1: + if not nArrays: + print ''' +static void +%s_unparse(struct ovsdb_idl_row *row_) +{ + struct %s *row = (struct %s *) row_; +''' % (structName, structName, structName) + if type.value: + keyVar = "row->key_%s" % columnName + valueVar = "row->value_%s" % columnName + else: + keyVar = "row->%s" % columnName + valueVar = None + print " free(%s);" % keyVar + if valueVar: + print " free(%s);" % valueVar + nArrays += 1 + if not nArrays: + print ''' +static void +%s_unparse(struct ovsdb_idl_row *row UNUSED) +{''' % (structName) + print "}" + + # First, next functions. + print ''' +const struct %(s)s *%(s)s_first(const struct ovsdb_idl *idl) +{ + return (const struct %(s)s *) ovsdb_idl_first_row(idl, &%(p)stable_classes[%(P)sTABLE_%(T)s]); +} + +const struct %(s)s *%(s)s_next(const struct %(s)s *row) +{ + return (const struct %(s)s *) ovsdb_idl_next_row(&row->header_); +}''' % {'s': structName, 'p': prefix, 'P': prefix.upper(), 'T': tableName.upper()} + + # Table columns. + print "\nstatic struct ovsdb_idl_column %s_columns[%s_N_COLUMNS] = {" % ( + structName, structName.upper()) + for columnName, column in table.columns.iteritems(): + type = column.type + + if type.value: + valueTypeName = type.value.upper() + else: + valueTypeName = "VOID" + if type.max == "unlimited": + max = "UINT_MAX" + else: + max = type.max + print " {\"%s\", {OVSDB_TYPE_%s, OVSDB_TYPE_%s, %d, %s}}," % ( + columnName, type.key.upper(), valueTypeName, + type.min, max) print "};" - print - print "#endif /* %(prefix)sIDL_HEADER */" % {'prefix': prefix.upper()} + + # Table classes. + print " " + print "static struct ovsdb_idl_table_class %stable_classes[%sN_TABLES] = {" % (prefix, prefix.upper()) + for tableName, table in schema.tables.iteritems(): + structName = "%s%s" % (prefix, tableName.lower()) + print " {\"%s\"," % tableName + print " %s_columns, ARRAY_SIZE(%s_columns)," % ( + structName, structName) + print " sizeof(struct %s)," % structName + print " %s_parse," % structName + print " %s_unparse}," % structName + print "};" + + # IDL class. + print "\nstruct ovsdb_idl_class %sidl_class = {" % prefix + print " %stable_classes, ARRAY_SIZE(%stable_classes)" % (prefix, prefix) + print "};" def ovsdb_escape(string): def escape(match): diff --git a/tests/automake.mk b/tests/automake.mk index b9d74836..cb962a84 100644 --- a/tests/automake.mk +++ b/tests/automake.mk @@ -31,6 +31,7 @@ TESTSUITE_AT = \ tests/ovsdb-file.at \ tests/ovsdb-server.at \ tests/ovsdb-monitor.at \ + tests/ovsdb-idl.at \ tests/stp.at \ tests/ovs-vsctl.at \ tests/lcov-post.at @@ -106,9 +107,12 @@ 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_SOURCES = tests/test-ovsdb.c tests/idltest.c tests/idltest.h tests_test_ovsdb_LDADD = ovsdb/libovsdb.a lib/libopenvswitch.a -EXTRA_DIST += tests/uuidfilt.pl +EXTRA_DIST += tests/uuidfilt.pl tests/idltest.ovsidl +BUILT_SOURCES += tests/idltest.c tests/idltest.h +noinst_DATA += tests/idltest.ovsschema +DISTCLEANFILES += tests/idltest.ovsschema noinst_PROGRAMS += tests/test-reconnect tests_test_reconnect_SOURCES = tests/test-reconnect.c diff --git a/tests/idltest.ovsidl b/tests/idltest.ovsidl new file mode 100644 index 00000000..ec5cec3e --- /dev/null +++ b/tests/idltest.ovsidl @@ -0,0 +1,29 @@ +// +// This is an ovsdb-idl schema. The OVSDB IDL compiler, ovsdb-idlc, +// can translate it into an OVSDB schema (which simply entails +// deleting some members from the schema) or C headers or source for +// use with the IDL at runtime. +// + +{"name": "idltest", + "idlPrefix": "idltest_", + "idlHeader": "\"tests/idltest.h\"", + "tables": { + "simple": { + "columns": { + "i": {"type": "integer"}, + "r": {"type": "real"}, + "b": {"type": "boolean"}, + "s": {"type": "string"}, + "u": {"type": "uuid"}, + "ia": {"type": {"key": "integer", "min": 0, "max": "unlimited"}}, + "ra": {"type": {"key": "real", "min": 0, "max": "unlimited"}}, + "ba": {"type": {"key": "boolean", "min": 0, "max": "unlimited"}}, + "sa": {"type": {"key": "string", "min": 0, "max": "unlimited"}}, + "ua": {"type": {"key": "uuid", "min": 0, "max": "unlimited"}}}}, + "selfLink": { + "columns": { + "i": {"type": "integer"}, + "k": {"type": {"key": "uuid", "keyRefTable": "selfLink"}}, + "ka": {"type": {"key": "uuid", "keyRefTable": "selfLink", + "min": 0, "max": "unlimited"}}}}}} diff --git a/tests/ovsdb-idl.at b/tests/ovsdb-idl.at new file mode 100644 index 00000000..f0e6ff8a --- /dev/null +++ b/tests/ovsdb-idl.at @@ -0,0 +1,266 @@ +AT_BANNER([OVSDB -- interface description language (IDL)]) + +# OVSDB_CHECK_IDL(TITLE, [PRE-IDL-TXN], TRANSACTIONS, OUTPUT, [KEYWORDS]) +# +# Creates a database with a schema derived from idltest.ovsidl, runs +# each PRE-IDL-TXN (if any), starts an ovsdb-server on that database, +# and runs "test-ovsdb idl" passing each of the TRANSACTIONS along. +# +# Checks that the overall output is OUTPUT. Before comparison, the +# output is sorted (using "sort") and 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. +# +# TITLE is provided to AT_SETUP and KEYWORDS to AT_KEYWORDS. +m4_define([OVSDB_CHECK_IDL], + [AT_SETUP([$1]) + AT_KEYWORDS([ovsdb server idl positive $5]) + OVS_CHECK_LCOV([ovsdb-tool create db $abs_builddir/idltest.ovsschema], + [0], [stdout], [ignore]) + AT_CHECK([ovsdb-server --detach --pidfile=$PWD/server-pid --listen=punix:socket --unixctl=$PWD/unixctl db]) + m4_if([$2], [], [], + [OVS_CHECK_LCOV([ovsdb-client transact unix:socket $2], [0], [ignore], [ignore], [kill `cat server-pid`])]) + AT_CHECK([test-ovsdb -vjsonrpc -t10 idl unix:socket $3], + [0], [stdout], [ignore], [kill `cat server-pid`]) + AT_CHECK([sort stdout | perl $srcdir/uuidfilt.pl], [0], [$4], [], + [kill `cat server-pid`]) + kill `cat server-pid` + AT_CLEANUP]) + +OVSDB_CHECK_IDL([simple idl, initially empty, no ops], + [], + [], + [000: empty +001: done +]) + +OVSDB_CHECK_IDL([simple idl, initially empty, various ops], + [], + [['[{"op": "insert", + "table": "simple", + "row": {"i": 1, + "r": 2.0, + "b": true, + "s": "mystring", + "u": ["uuid", "84f5c8f5-ac76-4dbc-a24f-8860eb407fc1"], + "ia": ["set", [1, 2, 3]], + "ra": ["set", [-0.5]], + "ba": ["set", [true, false]], + "sa": ["set", ["abc", "def"]], + "ua": ["set", [["uuid", "69443985-7806-45e2-b35f-574a04e720f9"], + ["uuid", "aad11ef0-816a-4b01-93e6-03b8b4256b98"]]]}}, + {"op": "insert", + "table": "simple", + "row": {}}]' \ + '[{"op": "update", + "table": "simple", + "where": [], + "row": {"b": true}}]' \ + '[{"op": "update", + "table": "simple", + "where": [], + "row": {"r": 123.5}}]' \ + '[{"op": "insert", + "table": "simple", + "row": {"i": -1, + "r": 125, + "b": false, + "s": "", + "ia": ["set", [1]], + "ra": ["set", [1.5]], + "ba": ["set", [false]], + "sa": ["set", []], + "ua": ["set", []]}}]' \ + '[{"op": "update", + "table": "simple", + "where": [["i", "<", 1]], + "row": {"s": "newstring"}}]' \ + '[{"op": "delete", + "table": "simple", + "where": [["i", "==", 0]]}]' \ + 'reconnect']], + [[000: empty +001: {"error":null,"result":[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]}]} +002: i=0 r=0 b=false s= u=<2> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<1> +002: i=1 r=2 b=true s=mystring u=<3> ia=[1 2 3] ra=[-0.5] ba=[false true] sa=[abc def] ua=[<4> <5>] uuid=<0> +003: {"error":null,"result":[{"count":2}]} +004: i=0 r=0 b=true s= u=<2> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<1> +004: i=1 r=2 b=true s=mystring u=<3> ia=[1 2 3] ra=[-0.5] ba=[false true] sa=[abc def] ua=[<4> <5>] uuid=<0> +005: {"error":null,"result":[{"count":2}]} +006: i=0 r=123.5 b=true s= u=<2> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<1> +006: i=1 r=123.5 b=true s=mystring u=<3> ia=[1 2 3] ra=[-0.5] ba=[false true] sa=[abc def] ua=[<4> <5>] uuid=<0> +007: {"error":null,"result":[{"uuid":["uuid","<6>"]}]} +008: i=-1 r=125 b=false s= u=<2> ia=[1] ra=[1.5] ba=[false] sa=[] ua=[] uuid=<6> +008: i=0 r=123.5 b=true s= u=<2> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<1> +008: i=1 r=123.5 b=true s=mystring u=<3> ia=[1 2 3] ra=[-0.5] ba=[false true] sa=[abc def] ua=[<4> <5>] uuid=<0> +009: {"error":null,"result":[{"count":2}]} +010: i=-1 r=125 b=false s=newstring u=<2> ia=[1] ra=[1.5] ba=[false] sa=[] ua=[] uuid=<6> +010: i=0 r=123.5 b=true s=newstring u=<2> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<1> +010: i=1 r=123.5 b=true s=mystring u=<3> ia=[1 2 3] ra=[-0.5] ba=[false true] sa=[abc def] ua=[<4> <5>] uuid=<0> +011: {"error":null,"result":[{"count":1}]} +012: i=-1 r=125 b=false s=newstring u=<2> ia=[1] ra=[1.5] ba=[false] sa=[] ua=[] uuid=<6> +012: i=1 r=123.5 b=true s=mystring u=<3> ia=[1 2 3] ra=[-0.5] ba=[false true] sa=[abc def] ua=[<4> <5>] uuid=<0> +013: reconnect +014: i=-1 r=125 b=false s=newstring u=<2> ia=[1] ra=[1.5] ba=[false] sa=[] ua=[] uuid=<6> +014: i=1 r=123.5 b=true s=mystring u=<3> ia=[1 2 3] ra=[-0.5] ba=[false true] sa=[abc def] ua=[<4> <5>] uuid=<0> +015: done +]]) + +OVSDB_CHECK_IDL([simple idl, initially populated], + [['[{"op": "insert", + "table": "simple", + "row": {"i": 1, + "r": 2.0, + "b": true, + "s": "mystring", + "u": ["uuid", "84f5c8f5-ac76-4dbc-a24f-8860eb407fc1"], + "ia": ["set", [1, 2, 3]], + "ra": ["set", [-0.5]], + "ba": ["set", [true, false]], + "sa": ["set", ["abc", "def"]], + "ua": ["set", [["uuid", "69443985-7806-45e2-b35f-574a04e720f9"], + ["uuid", "aad11ef0-816a-4b01-93e6-03b8b4256b98"]]]}}, + {"op": "insert", + "table": "simple", + "row": {}}]']], + [['[{"op": "update", + "table": "simple", + "where": [], + "row": {"b": true}}]']], + [[000: i=0 r=0 b=false s= u=<0> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<1> +000: i=1 r=2 b=true s=mystring u=<2> ia=[1 2 3] ra=[-0.5] ba=[false true] sa=[abc def] ua=[<3> <4>] uuid=<5> +001: {"error":null,"result":[{"count":2}]} +002: i=0 r=0 b=true s= u=<0> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<1> +002: i=1 r=2 b=true s=mystring u=<2> ia=[1 2 3] ra=[-0.5] ba=[false true] sa=[abc def] ua=[<3> <4>] uuid=<5> +003: done +]]) + +OVSDB_CHECK_IDL([self-linking idl, consistent ops], + [], + [['[{"op": "insert", + "table": "selfLink", + "row": {"i": 0, "k": ["named-uuid", "self"]}, + "uuid-name": "self"}]' \ + '[{"op": "insert", + "table": "selfLink", + "row": {"i": 1}, + "uuid-name": "row1"}, + {"op": "insert", + "table": "selfLink", + "row": {"i": 2, "k": ["named-uuid", "row1"]}, + "uuid-name": "row2"}, + {"op": "update", + "table": "selfLink", + "where": [["i", "==", 1]], + "row": {"k": ["named-uuid", "row2"]}}]' \ + '[{"op": "update", + "table": "selfLink", + "where": [["i", "==", 1]], + "row": {"k": ["uuid", "#1#"]}}]' \ + '[{"op": "update", + "table": "selfLink", + "where": [], + "row": {"k": ["uuid", "#0#"]}}]']], + [[000: empty +001: {"error":null,"result":[{"uuid":["uuid","<0>"]}]} +002: i=0 k=0 ka=[] uuid=<0> +003: {"error":null,"result":[{"uuid":["uuid","<1>"]},{"uuid":["uuid","<2>"]},{"count":1}]} +004: i=0 k=0 ka=[] uuid=<0> +004: i=1 k=2 ka=[] uuid=<1> +004: i=2 k=1 ka=[] uuid=<2> +005: {"error":null,"result":[{"count":1}]} +006: i=0 k=0 ka=[] uuid=<0> +006: i=1 k=1 ka=[] uuid=<1> +006: i=2 k=1 ka=[] uuid=<2> +007: {"error":null,"result":[{"count":3}]} +008: i=0 k=0 ka=[] uuid=<0> +008: i=1 k=0 ka=[] uuid=<1> +008: i=2 k=0 ka=[] uuid=<2> +009: done +]]) + +OVSDB_CHECK_IDL([self-linking idl, inconsistent ops], + [], + [['[{"op": "insert", + "table": "selfLink", + "row": {"i": 0, "k": ["uuid", "cf197cc5-c8c9-42f5-82d5-c71a9f2cb96b"]}}]' \ + '[{"op": "update", + "table": "selfLink", + "where": [], + "row": {"k": ["uuid", "#0#"]}}]' \ + '[{"op": "update", + "table": "selfLink", + "where": [], + "row": {"k": ["uuid", "c2fca39a-e69a-42a4-9c56-5eca85839ce9"]}}]' \ + '[{"op": "insert", + "table": "selfLink", + "row": {"i": 1, "k": ["uuid", "52d752a3-b062-4668-9446-d2e0d4a14703"]}}]' \ + '[{"op": "update", + "table": "selfLink", + "where": [], + "row": {"k": ["uuid", "#1#"]}}]' \ +]], + [[000: empty +001: {"error":null,"result":[{"uuid":["uuid","<0>"]}]} +002: i=0 k= ka=[] uuid=<0> +003: {"error":null,"result":[{"count":1}]} +004: i=0 k=0 ka=[] uuid=<0> +005: {"error":null,"result":[{"count":1}]} +006: i=0 k= ka=[] uuid=<0> +007: {"error":null,"result":[{"uuid":["uuid","<1>"]}]} +008: i=0 k= ka=[] uuid=<0> +008: i=1 k= ka=[] uuid=<1> +009: {"error":null,"result":[{"count":2}]} +010: i=0 k=1 ka=[] uuid=<0> +010: i=1 k=1 ka=[] uuid=<1> +011: done +]]) + +OVSDB_CHECK_IDL([self-linking idl, sets], + [], + [['[{"op": "insert", + "table": "selfLink", + "row": {"i": 0, "ka": ["set", [["named-uuid", "i0"]]]}, + "uuid-name": "i0"}, + {"op": "insert", + "table": "selfLink", + "row": {"i": 1, "ka": ["set", [["named-uuid", "i1"]]]}, + "uuid-name": "i1"}, + {"op": "insert", + "table": "selfLink", + "row": {"i": 2, "ka": ["set", [["named-uuid", "i2"]]]}, + "uuid-name": "i2"}, + {"op": "insert", + "table": "selfLink", + "row": {"i": 3, "ka": ["set", [["named-uuid", "i3"]]]}, + "uuid-name": "i3"}]' \ + '[{"op": "update", + "table": "selfLink", + "where": [], + "row": {"ka": ["set", [["uuid", "#0#"], ["uuid", "#1#"], ["uuid", "#2#"], ["uuid", "#3#"]]]}}]' \ + '[{"op": "update", + "table": "selfLink", + "where": [], + "row": {"ka": ["set", [["uuid", "#0#"], ["uuid", "88702e78-845b-4a6e-ad08-cf68922ae84a"], ["uuid", "#2#"], ["uuid", "1ac2b12e-b767-4805-a55d-43976e40c465"]]]}}]']], + [[000: empty +001: {"error":null,"result":[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"uuid":["uuid","<2>"]},{"uuid":["uuid","<3>"]}]} +002: i=0 k= ka=[0] uuid=<0> +002: i=1 k= ka=[1] uuid=<1> +002: i=2 k= ka=[2] uuid=<2> +002: i=3 k= ka=[3] uuid=<3> +003: {"error":null,"result":[{"count":4}]} +004: i=0 k= ka=[0 1 2 3] uuid=<0> +004: i=1 k= ka=[0 1 2 3] uuid=<1> +004: i=2 k= ka=[0 1 2 3] uuid=<2> +004: i=3 k= ka=[0 1 2 3] uuid=<3> +005: {"error":null,"result":[{"count":4}]} +006: i=0 k= ka=[0 2] uuid=<0> +006: i=1 k= ka=[0 2] uuid=<1> +006: i=2 k= ka=[0 2] uuid=<2> +006: i=3 k= ka=[0 2] uuid=<3> +007: done +]]) + +# XXX self-linking idl, maps diff --git a/tests/ovsdb.at b/tests/ovsdb.at index 690e9987..6e3f63d8 100644 --- a/tests/ovsdb.at +++ b/tests/ovsdb.at @@ -49,3 +49,4 @@ m4_include([tests/ovsdb-trigger.at]) m4_include([tests/ovsdb-file.at]) m4_include([tests/ovsdb-server.at]) m4_include([tests/ovsdb-monitor.at]) +m4_include([tests/ovsdb-idl.at]) diff --git a/tests/test-ovsdb.c b/tests/test-ovsdb.c index 27523fb2..f3305edd 100644 --- a/tests/test-ovsdb.c +++ b/tests/test-ovsdb.c @@ -25,8 +25,10 @@ #include "command-line.h" #include "json.h" +#include "jsonrpc.h" #include "ovsdb-data.h" #include "ovsdb-error.h" +#include "ovsdb-idl.h" #include "ovsdb-types.h" #include "ovsdb/column.h" #include "ovsdb/condition.h" @@ -39,7 +41,9 @@ #include "ovsdb/transaction.h" #include "ovsdb/trigger.h" #include "poll-loop.h" +#include "stream.h" #include "svec.h" +#include "tests/idltest.h" #include "timeval.h" #include "util.h" #include "vlog.h" @@ -64,6 +68,7 @@ static void parse_options(int argc, char *argv[]) { static struct option long_options[] = { + {"timeout", required_argument, 0, 't'}, {"verbose", optional_argument, 0, 'v'}, {"help", no_argument, 0, 'h'}, {0, 0, 0, 0}, @@ -71,12 +76,25 @@ parse_options(int argc, char *argv[]) char *short_options = long_options_to_short_options(long_options); for (;;) { - int c = getopt_long(argc, argv, short_options, long_options, NULL); + unsigned long int timeout; + int c; + + c = getopt_long(argc, argv, short_options, long_options, NULL); if (c == -1) { break; } switch (c) { + case 't': + timeout = strtoul(optarg, NULL, 10); + if (timeout <= 0) { + ovs_fatal(0, "value %s on -t or --timeout is not at least 1", + optarg); + } else { + time_alarm(timeout); + } + break; + case 'h': usage(); @@ -144,10 +162,16 @@ usage(void) " 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", + " simulated time, for causing triggers to time out.\n" + " idl SERVER [TRANSACTION...]\n" + " connect to SERVER and dump the contents of the database\n" + " as seen initially by the IDL implementation and after\n" + " executing each TRANSACTION. (Each TRANSACTION must modify\n" + " the database or this command will hang.)\n", program_name, program_name); vlog_usage(); printf("\nOther options:\n" + " -t, --timeout=SECS give up after SECS seconds\n" " -h, --help display this help message\n"); exit(EXIT_SUCCESS); } @@ -1207,6 +1231,215 @@ do_transact(int argc, char *argv[]) ovsdb_destroy(do_transact_db); /* Also destroys 'schema'. */ } +static int +compare_selflink(const void *a_, const void *b_) +{ + const struct idltest_selflink *const *ap = a_; + const struct idltest_selflink *const *bp = b_; + const struct idltest_selflink *a = *ap; + const struct idltest_selflink *b = *bp; + + + return a->i < b->i ? -1 : a->i > b->i; +} + +static void +print_idl(struct ovsdb_idl *idl, int step) +{ + const struct idltest_simple *s; + const struct idltest_selflink *sl; + int n = 0; + + IDLTEST_SIMPLE_FOR_EACH (s, idl) { + size_t i; + + printf("%03d: i=%"PRId64" r=%g b=%s s=%s u="UUID_FMT" ia=[", + step, s->i, s->r, s->b ? "true" : "false", + s->s, UUID_ARGS(&s->u)); + for (i = 0; i < s->n_ia; i++) { + printf("%s%"PRId64, i ? " " : "", s->ia[i]); + } + printf("] ra=["); + for (i = 0; i < s->n_ra; i++) { + printf("%s%g", i ? " " : "", s->ra[i]); + } + printf("] ba=["); + for (i = 0; i < s->n_ba; i++) { + printf("%s%s", i ? " " : "", s->ba[i] ? "true" : "false"); + } + printf("] sa=["); + for (i = 0; i < s->n_sa; i++) { + printf("%s%s", i ? " " : "", s->sa[i]); + } + printf("] ua=["); + for (i = 0; i < s->n_ua; i++) { + printf("%s"UUID_FMT, i ? " " : "", UUID_ARGS(&s->ua[i])); + } + printf("] uuid="UUID_FMT"\n", UUID_ARGS(&s->header_.uuid)); + n++; + } + IDLTEST_SELFLINK_FOR_EACH (sl, idl) { + struct idltest_selflink **links; + size_t i; + + printf("%03d: i=%"PRId64" k=", step, sl->i); + if (sl->k) { + printf("%"PRId64, sl->k->i); + } + printf(" ka=["); + links = xmemdup(sl->ka, sl->n_ka * sizeof *sl->ka); + qsort(links, sl->n_ka, sizeof *links, compare_selflink); + for (i = 0; i < sl->n_ka; i++) { + printf("%s%"PRId64, i ? " " : "", links[i]->i); + } + free(links); + printf("] uuid="UUID_FMT"\n", UUID_ARGS(&sl->header_.uuid)); + n++; + } + if (!n) { + printf("%03d: empty\n", step); + } +} + +static unsigned int +print_updated_idl(struct ovsdb_idl *idl, struct jsonrpc *rpc, + int step, unsigned int seqno) +{ + for (;;) { + unsigned int new_seqno; + + if (rpc) { + jsonrpc_run(rpc); + } + ovsdb_idl_run(idl); + new_seqno = ovsdb_idl_get_seqno(idl); + if (new_seqno != seqno) { + print_idl(idl, step); + return new_seqno; + } + + if (rpc) { + jsonrpc_wait(rpc); + } + ovsdb_idl_wait(idl); + poll_block(); + } +} + +static void +parse_uuids(const struct json *json, struct ovsdb_symbol_table *symtab, + size_t *n) +{ + struct uuid uuid; + + if (json->type == JSON_STRING && uuid_from_string(&uuid, json->u.string)) { + char *name = xasprintf("#%d#", *n); + ovsdb_symbol_table_put(symtab, name, &uuid); + free(name); + *n += 1; + } else if (json->type == JSON_ARRAY) { + size_t i; + + for (i = 0; i < json->u.array.n; i++) { + parse_uuids(json->u.array.elems[i], symtab, n); + } + } else if (json->type == JSON_OBJECT) { + const struct shash_node *node; + + SHASH_FOR_EACH (node, json_object(json)) { + parse_uuids(node->data, symtab, n); + } + } +} + +static void +substitute_uuids(struct json *json, const struct ovsdb_symbol_table *symtab) +{ + if (json->type == JSON_STRING) { + const struct uuid *uuid; + + uuid = ovsdb_symbol_table_get(symtab, json->u.string); + if (uuid) { + free(json->u.string); + json->u.string = xasprintf(UUID_FMT, UUID_ARGS(uuid)); + } + } else if (json->type == JSON_ARRAY) { + size_t i; + + for (i = 0; i < json->u.array.n; i++) { + substitute_uuids(json->u.array.elems[i], symtab); + } + } else if (json->type == JSON_OBJECT) { + const struct shash_node *node; + + SHASH_FOR_EACH (node, json_object(json)) { + substitute_uuids(node->data, symtab); + } + } +} + +static void +do_idl(int argc, char *argv[]) +{ + struct jsonrpc *rpc; + struct ovsdb_idl *idl; + unsigned int seqno = 0; + struct ovsdb_symbol_table *symtab; + size_t n_uuids = 0; + int step = 0; + int error; + int i; + + idl = ovsdb_idl_create(argv[1], &idltest_idl_class); + if (argc > 2) { + struct stream *stream; + + error = stream_open_block(argv[1], &stream); + if (error) { + ovs_fatal(error, "failed to connect to \"%s\"", argv[1]); + } + rpc = jsonrpc_open(stream); + } else { + rpc = NULL; + } + + symtab = ovsdb_symbol_table_create(); + for (i = 2; i < argc; i++) { + struct jsonrpc_msg *request, *reply; + int error; + + seqno = print_updated_idl(idl, rpc, step++, seqno); + + if (!strcmp(argv[i], "reconnect")) { + printf("%03d: reconnect\n", step++); + ovsdb_idl_force_reconnect(idl); + } else { + struct json *json = parse_json(argv[i]); + substitute_uuids(json, symtab); + request = jsonrpc_create_request("transact", json, NULL); + error = jsonrpc_transact_block(rpc, request, &reply); + if (error) { + ovs_fatal(error, "jsonrpc transaction failed"); + } + printf("%03d: ", step++); + if (reply->result) { + parse_uuids(reply->result, symtab, &n_uuids); + } + json_destroy(reply->id); + reply->id = NULL; + print_and_free_json(jsonrpc_msg_to_json(reply)); + } + } + ovsdb_symbol_table_destroy(symtab); + + if (rpc) { + jsonrpc_close(rpc); + } + print_updated_idl(idl, NULL, step++, seqno); + ovsdb_idl_destroy(idl); + printf("%03d: done\n", step); +} + static struct command all_commands[] = { { "log-io", 2, INT_MAX, do_log_io }, { "parse-atomic-type", 1, 1, do_parse_atomic_type }, @@ -1225,6 +1458,7 @@ static struct command all_commands[] = { { "transact", 1, INT_MAX, do_transact }, { "execute", 2, INT_MAX, do_execute }, { "trigger", 2, INT_MAX, do_trigger }, + { "idl", 1, INT_MAX, do_idl }, { "help", 0, INT_MAX, do_help }, { NULL, 0, 0, NULL }, }; diff --git a/vswitchd/automake.mk b/vswitchd/automake.mk index 6860488f..db8fdf69 100644 --- a/vswitchd/automake.mk +++ b/vswitchd/automake.mk @@ -17,6 +17,7 @@ vswitchd_ovs_vswitchd_SOURCES = \ vswitchd/proc-net-compat.h \ vswitchd/ovs-vswitchd.c \ vswitchd/ovs-vswitchd.h \ + vswitchd/vswitch-idl.c \ vswitchd/vswitch-idl.h \ vswitchd/xenserver.c \ vswitchd/xenserver.h @@ -38,10 +39,8 @@ EXTRA_DIST += \ vswitchd/ovs-vswitchd.8.in \ vswitchd/ovs-brcompatd.8.in -EXTRA_DIST += vswitchd/vswitch.ovsidl -BUILT_SOURCES += vswitchd/vswitch-idl.h -DISTCLEANFILES += \ - vswitchd/vswitch.ovsidl \ - vswitchd/vswitch-idl.h -vswitchd/vswitch-idl.h: vswitchd/vswitch.ovsidl ovsdb/ovsdb-idlc - ovsdb/ovsdb-idlc c-idl-header $(srcdir)/vswitchd/vswitch.ovsidl > $@ +EXTRA_DIST += vswitchd/vswitch-idl.ovsidl +BUILT_SOURCES += vswitchd/vswitch-idl.c vswitchd/vswitch-idl.h +DISTCLEANFILES += vswitchd/vswitch-idl.c vswitchd/vswitch-idl.h +noinst_DATA += vswitchd/vswitch-idl.ovsschema +DISTCLEANFILES += vswitchd/vswitch-idl.ovsschema diff --git a/vswitchd/vswitch-idl.ovsidl b/vswitchd/vswitch-idl.ovsidl new file mode 100644 index 00000000..df50bf02 --- /dev/null +++ b/vswitchd/vswitch-idl.ovsidl @@ -0,0 +1,175 @@ +// +// This is an ovsdb-idl schema. The OVSDB IDL compiler, ovsdb-idlc, +// can translate it into an OVSDB schema (which simply entails +// deleting some members from the schema) or C headers or source for +// use with the IDL at runtime. +// + +{"name": "ovs_vswitchd_db", + "comment": "Configuration for one Open vSwitch daemon.", + "idlPrefix": "ovsrec_", + "idlHeader": "\"vswitchd/vswitch-idl.h\"", + "tables": { + "Open_vSwitch": { + "comment": "Configuration for an Open vSwitch daemon.", + "columns": { + "bridges": { + "comment": "Set of bridges managed by the daemon.", + "type": {"key": "uuid", "keyRefTable": "Bridge", + "min": 0, "max": "unlimited"}}, + "management_id": { + "comment": "Exactly 12 hex digits that identify the daemon.", + "type": "string"}, + "controller": { + "comment": "Default Controller used by bridges.", + "type": {"key": "uuid", "keyRefTable": "Controller", "min": 0, "max": 1}}, + "ssl": { + "comment": "SSL used globally by the daemon.", + "type": {"key": "uuid", "keyRefTable": "SSL", "min": 0, "max": 1}}}}, + "Bridge": { + "comment": "Configuration for a bridge within an Open_vSwitch.", + "columns": { + "name": { + "comment": "Bridge identifier. Should be alphanumeric and no more than about 8 bytes long. Must be unique among the names of ports, interfaces, and bridges on a host.", + "type": "string"}, + "datapath_id": { + "comment": "OpenFlow datapath ID. Exactly 12 hex digits.", + "type": {"key": "string", "min": 0, "max": 1}}, + "hwaddr": { + "comment": "Ethernet address to use for bridge. Exactly 12 hex digits in the form XX:XX:XX:XX:XX:XX.", + "type": {"key": "string", "min": 0, "max": 1}}, + "ports": { + "comment": "Ports included in the bridge.", + "type": {"key": "uuid", "keyRefTable": "Port", "min": 0, "max": "unlimited"}}, + "mirrors": { + "comment": "Port mirroring configuration.", + "type": {"key": "uuid", "keyRefTable": "Mirror", "min": 0, "max": "unlimited"}}, + "netflow": { + "comment": "NetFlow configuration.", + "type": {"key": "uuid", "keyRefTable": "NetFlow", "min": 0, "max": "unlimited"}}, + "controller": { + "comment": "OpenFlow controller. If unset, defaults to that specified by the parent Open_vSwitch.", + "type": {"key": "uuid", "keyRefTable": "Controller", "min": 0, "max": 1}}}}, + "Port": { + "comment": "A port within a Bridge. May contain a single Interface or multiple (bonded) Interfaces.", + "columns": { + "name": { + "comment": "Port name. Should be alphanumeric and no more than about 8 bytes long. May be the same as the interface name, for non-bonded ports. Must otherwise be unique among the names of ports, interfaces, and bridges on a host.", + "type": "string"}, + "interfaces": { + "comment": "The Port's Interfaces. If there is more than one, this is a bonded Port.", + "type": {"key": "uuid", "keyRefTable": "Interface", "min": 1, "max": "unlimited"}}, + "trunks": { + "comment": "The 802.1Q VLAN(s) that this port trunks. Should be empty if this port trunks all VLAN(s) or if this is not a trunk port.", + "type": {"key": "integer", "min": 0, "max": 4096}}, + "tag": { + "comment": "This port's implicitly tagged VLAN. Should be empty if this is a trunk port.", + "type": {"key": "integer", "min": 0, "max": 1}}, + "updelay": { + "comment": "For a bonded port, the number of milliseconds for which carrier must stay up on an interface before the interface is considered to be up. Ignored for non-bonded ports.", + "type": "integer"}, + "downdelay": { + "comment": "For a bonded port, the number of milliseconds for which carrier must stay down on an interface before the interface is considered to be down. Ignored for non-bonded ports.", + "type": "integer"}}}, + "Interface": { + "comment": "An interface within a Port.", + "columns": { + "name": { + "comment": "Interface name. Should be alphanumeric and no more than about 8 bytes long. May be the same as the port name, for non-bonded ports. Must otherwise be unique among the names of ports, interfaces, and bridges on a host.", + "type": "string"}, + "internal": { + "comment": "An \"internal\" port is one that is implemented in software as a logical device.", + "type": "boolean"}, + "ingress_policing_rate": { + "comment": "Maximum rate for data received on this interface, in kbps. Set to 0 to disable policing.", + "type": "integer"}, + "ingress_policing_burst": { + "comment": "Maximum burst size for data received on this interface, in kb. The default burst size if set to 0 is 10 kb.", + "type": "integer"}}}, + "Mirror": { + "comment": "A port mirror within a Bridge.", + "columns": { + "name": { + "comment": "Arbitrary identifier for the Mirror.", + "type": "string"}, + "select_src_port": { + "comment": "Ports on which arriving packets are selected for mirroring.", + "type": {"key": "uuid", "keyRefTable": "Port", "min": 0, "max": "unlimited"}}, + "select_dst_port": { + "comment": "Ports on which departing packets are selected for mirroring.", + "type": {"key": "uuid", "keyRefTable": "Port", "min": 0, "max": "unlimited"}}, + "select_vlan": { + "comment": "VLANs on which packets are selected for mirroring.", + "type": {"key": "integer", "min": 0, "max": 4096}}, + "output_port": { + "comment": "Output port for selected packets. Mutually exclusive with output_vlan.", + "type": {"key": "uuid", "keyRefTable": "Port", "min": 0, "max": 1}}, + "output_vlan": { + "comment": "Output VLAN for selected packets. Mutually exclusive with output_port.", + "type": {"key": "integer", "min": 0, "max": 1}}}}, + "NetFlow": { + "comment": "A NetFlow target.", + "columns": { + "target": { + "comment": "NetFlow target in the form \"IP:PORT\".", + "type": "string"}, + "engine_type": { + "comment": "Engine type to use in NetFlow messages. Defaults to datapath index if not specified.", + "type": "integer", "min":0, "max":1}, + "engine_id": { + "comment": "Engine ID to use in NetFlow messages. Defaults to datapath index if not specified.", + "type": "integer", "min":0, "max":1}, + "add_id_to_interface": { + "comment": "Place least-significant 7 bits of engine ID into most significant bits of ingress and egress interface fields of NetFlow records?", + "type": "boolean"}}}, + "Controller": { + "comment": "An OpenFlow controller.", + "columns": { + "target": { + "comment": "Connection method for controller, e.g. \"ssl:...\", \"tcp:...\". The special string \"discover\" enables controller discovery.", + "type": "string"}, + "max_backoff": { + "comment": "Maximum number of milliseconds to wait between connection attempts. Default is implementation-specific.", + "type": {"key": "integer", "min": 0, "max": 1}}, + "inactivity_probe": { + "comment": "Maximum number of milliseconds of idle time on connection to controller before sending an inactivity probe message. Default is implementation-specific.", + "type": {"key": "integer", "min": 0, "max": 1}}, + "fail_mode": { + "comment": "Either \"standalone\" or \"secure\", or empty to use the implementation's default.", + "type": {"key": "string", "min": 0, "max": 1}}, + "discover_accept_regex": { + "comment": "If \"target\" is \"discover\", a POSIX extended regular expression against which the discovered controller location is validated. If not specified, the default is implementation-specific.", + "type": {"key": "string", "min": 0, "max": 1}}, + "discover_update_resolv_conf": { + "comment": "If \"target\" is \"discover\", whether to update /etc/resolv.conf when the controller is discovered. If not specified, the default is implementation-specific.", + "type": {"key": "boolean", "min": 0, "max": 1}}, + "connection_mode": { + "comment": "Either \"in-band\" or \"out-of-band\". If not specified, the default is implementation-specific.", + "type": {"key": "string", "min": 0, "max": 1}}, + "local_ip": { + "comment": "If \"target\" is not \"discover\", the IP address to configure on the local port.", + "type": {"key": "string", "min": 0, "max": 1}}, + "local_netmask": { + "comment": "If \"target\" is not \"discover\", the IP netmask to configure on the local port.", + "type": {"key": "string", "min": 0, "max": 1}}, + "local_gateway": { + "comment": "If \"target\" is not \"discover\", the IP gateway to configure on the local port.", + "type": {"key": "string", "min": 0, "max": 1}}, + "controller_rate_limit": { + "comment": "The maximum rate at which packets will be forwarded to the OpenFlow controller, in packets per second. If not specified, the default is implementation-specific.", + "type": {"key": "integer", "min": 0, "max": 1}}, + "controller_burst_limit": { + "comment": "The maximum number of unused packet credits that the bridge will allow to accumulate, in packets. If not specified, the default is implementation-specific.", + "type": {"key": "integer", "min": 0, "max": 1}}}}, + "SSL": { + "comment": "SSL configuration for an Open_vSwitch.", + "columns": { + "private_key": { + "comment": "Name of a PEM file containing the private key used as the switch's identity for SSL connections to the controller.", + "type": "string"}, + "certificate": { + "comment": "Name of a PEM file containing a certificate, signed by the certificate authority (CA) used by the controller and manager, that certifies the switch's private key, identifying a trustworthy switch.", + "type": "string"}, + "ca_cert": { + "comment": "Name of a PEM file containing the CA certificate used to verify that the switch is connected to a trustworthy controller.", + "type": "string"}}}}} diff --git a/vswitchd/vswitch.ovsidl b/vswitchd/vswitch.ovsidl deleted file mode 100644 index 263d7fdc..00000000 --- a/vswitchd/vswitch.ovsidl +++ /dev/null @@ -1,173 +0,0 @@ -// -// This is an ovsdb-idl schema. The OVSDB IDL compiler, ovsdb-idlc, -// can translate it into an OVSDB schema (which simply entails -// deleting some members from the schema) or C headers or source for -// use with the IDL at runtime. -// - -{"name": "ovs_vswitchd_db", - "comment": "Configuration for one Open vSwitch daemon.", - "tables": { - "Open_vSwitch": { - "comment": "Configuration for an Open vSwitch daemon.", - "columns": { - "bridges": { - "comment": "Set of bridges managed by the daemon.", - "type": {"key": "uuid", "keyRefTable": "Bridge", - "min": 0, "max": "unlimited"}}, - "management_id": { - "comment": "Exactly 12 hex digits that identify the daemon.", - "type": "string"}, - "controller": { - "comment": "Default Controller used by bridges.", - "type": {"key": "uuid", "keyRefTable": "Controller", "min": 0, "max": 1}}, - "ssl": { - "comment": "SSL used globally by the daemon.", - "type": {"key": "uuid", "keyRefTable": "SSL", "min": 0, "max": 1}}}}, - "Bridge": { - "comment": "Configuration for a bridge within an Open_vSwitch.", - "columns": { - "name": { - "comment": "Bridge identifier. Should be alphanumeric and no more than about 8 bytes long. Must be unique among the names of ports, interfaces, and bridges on a host.", - "type": "string"}, - "datapath_id": { - "comment": "OpenFlow datapath ID. Exactly 12 hex digits.", - "type": {"key": "string", "min": 0, "max": 1}}, - "hwaddr": { - "comment": "Ethernet address to use for bridge. Exactly 12 hex digits in the form XX:XX:XX:XX:XX:XX.", - "type": {"key": "string", "min": 0, "max": 1}}, - "ports": { - "comment": "Ports included in the bridge.", - "type": {"key": "uuid", "keyRefTable": "Port", "min": 0, "max": "unlimited"}}, - "mirrors": { - "comment": "Port mirroring configuration.", - "type": {"key": "uuid", "keyRefTable": "Mirror", "min": 0, "max": "unlimited"}}, - "netflow": { - "comment": "NetFlow configuration.", - "type": {"key": "uuid", "keyRefTable": "NetFlow", "min": 0, "max": "unlimited"}}, - "controller": { - "comment": "OpenFlow controller. If unset, defaults to that specified by the parent Open_vSwitch.", - "type": {"key": "uuid", "keyRefTable": "Controller", "min": 0, "max": 1}}}}, - "Port": { - "comment": "A port within a Bridge. May contain a single Interface or multiple (bonded) Interfaces.", - "columns": { - "name": { - "comment": "Port name. Should be alphanumeric and no more than about 8 bytes long. May be the same as the interface name, for non-bonded ports. Must otherwise be unique among the names of ports, interfaces, and bridges on a host.", - "type": "string"}, - "interfaces": { - "comment": "The Port's Interfaces. If there is more than one, this is a bonded Port.", - "type": {"key": "uuid", "keyRefTable": "Interface", "min": 1, "max": "unlimited"}}, - "trunks": { - "comment": "The 802.1Q VLAN(s) that this port trunks. Should be empty if this port trunks all VLAN(s) or if this is not a trunk port.", - "type": {"key": "integer", "min": 0, "max": 4096}}, - "tag": { - "comment": "This port's implicitly tagged VLAN. Should be empty if this is a trunk port.", - "type": {"key": "integer", "min": 0, "max": 1}}, - "updelay": { - "comment": "For a bonded port, the number of milliseconds for which carrier must stay up on an interface before the interface is considered to be up. Ignored for non-bonded ports.", - "type": "integer"}, - "downdelay": { - "comment": "For a bonded port, the number of milliseconds for which carrier must stay down on an interface before the interface is considered to be down. Ignored for non-bonded ports.", - "type": "integer"}}}, - "Interface": { - "comment": "An interface within a Port.", - "columns": { - "name": { - "comment": "Interface name. Should be alphanumeric and no more than about 8 bytes long. May be the same as the port name, for non-bonded ports. Must otherwise be unique among the names of ports, interfaces, and bridges on a host.", - "type": "string"}, - "internal": { - "comment": "An \"internal\" port is one that is implemented in software as a logical device.", - "type": "boolean"}, - "ingress_policing_rate": { - "comment": "Maximum rate for data received on this interface, in kbps. Set to 0 to disable policing.", - "type": "integer"}, - "ingress_policing_burst": { - "comment": "Maximum burst size for data received on this interface, in kb. The default burst size if set to 0 is 10 kb.", - "type": "integer"}}}, - "Mirror": { - "comment": "A port mirror within a Bridge.", - "columns": { - "name": { - "comment": "Arbitrary identifier for the Mirror.", - "type": "string"}, - "select_src_port": { - "comment": "Ports on which arriving packets are selected for mirroring.", - "type": {"key": "uuid", "keyRefTable": "Port", "min": 0, "max": "unlimited"}}, - "select_dst_port": { - "comment": "Ports on which departing packets are selected for mirroring.", - "type": {"key": "uuid", "keyRefTable": "Port", "min": 0, "max": "unlimited"}}, - "select_vlan": { - "comment": "VLANs on which packets are selected for mirroring.", - "type": {"key": "integer", "min": 0, "max": 4096}}, - "output_port": { - "comment": "Output port for selected packets. Mutually exclusive with output_vlan.", - "type": {"key": "uuid", "keyRefTable": "Port", "min": 0, "max": 1}}, - "output_vlan": { - "comment": "Output VLAN for selected packets. Mutually exclusive with output_port.", - "type": {"key": "integer", "min": 0, "max": 1}}}}, - "NetFlow": { - "comment": "A NetFlow target.", - "columns": { - "target": { - "comment": "NetFlow target in the form \"IP:PORT\".", - "type": "string"}, - "engine_type": { - "comment": "Engine type to use in NetFlow messages. Defaults to datapath index if not specified.", - "type": "integer", "min":0, "max":1}, - "engine_id": { - "comment": "Engine ID to use in NetFlow messages. Defaults to datapath index if not specified.", - "type": "integer", "min":0, "max":1}, - "add_id_to_interface": { - "comment": "Place least-significant 7 bits of engine ID into most significant bits of ingress and egress interface fields of NetFlow records?", - "type": "boolean"}}}, - "Controller": { - "comment": "An OpenFlow controller.", - "columns": { - "target": { - "comment": "Connection method for controller, e.g. \"ssl:...\", \"tcp:...\". The special string \"discover\" enables controller discovery.", - "type": "string"}, - "max_backoff": { - "comment": "Maximum number of milliseconds to wait between connection attempts. Default is implementation-specific.", - "type": {"key": "integer", "min": 0, "max": 1}}, - "inactivity_probe": { - "comment": "Maximum number of milliseconds of idle time on connection to controller before sending an inactivity probe message. Default is implementation-specific.", - "type": {"key": "integer", "min": 0, "max": 1}}, - "fail_mode": { - "comment": "Either \"standalone\" or \"secure\", or empty to use the implementation's default.", - "type": {"key": "string", "min": 0, "max": 1}}, - "discover_accept_regex": { - "comment": "If \"target\" is \"discover\", a POSIX extended regular expression against which the discovered controller location is validated. If not specified, the default is implementation-specific.", - "type": {"key": "string", "min": 0, "max": 1}}, - "discover_update_resolv_conf": { - "comment": "If \"target\" is \"discover\", whether to update /etc/resolv.conf when the controller is discovered. If not specified, the default is implementation-specific.", - "type": {"key": "boolean", "min": 0, "max": 1}}, - "connection_mode": { - "comment": "Either \"in-band\" or \"out-of-band\". If not specified, the default is implementation-specific.", - "type": {"key": "string", "min": 0, "max": 1}}, - "local_ip": { - "comment": "If \"target\" is not \"discover\", the IP address to configure on the local port.", - "type": {"key": "string", "min": 0, "max": 1}}, - "local_netmask": { - "comment": "If \"target\" is not \"discover\", the IP netmask to configure on the local port.", - "type": {"key": "string", "min": 0, "max": 1}}, - "local_gateway": { - "comment": "If \"target\" is not \"discover\", the IP gateway to configure on the local port.", - "type": {"key": "string", "min": 0, "max": 1}}, - "controller_rate_limit": { - "comment": "The maximum rate at which packets will be forwarded to the OpenFlow controller, in packets per second. If not specified, the default is implementation-specific.", - "type": {"key": "integer", "min": 0, "max": 1}}, - "controller_burst_limit": { - "comment": "The maximum number of unused packet credits that the bridge will allow to accumulate, in packets. If not specified, the default is implementation-specific.", - "type": {"key": "integer", "min": 0, "max": 1}}}}, - "SSL": { - "comment": "SSL configuration for an Open_vSwitch.", - "columns": { - "private_key": { - "comment": "Name of a PEM file containing the private key used as the switch's identity for SSL connections to the controller.", - "type": "string"}, - "certificate": { - "comment": "Name of a PEM file containing a certificate, signed by the certificate authority (CA) used by the controller and manager, that certifies the switch's private key, identifying a trustworthy switch.", - "type": "string"}, - "ca_cert": { - "comment": "Name of a PEM file containing the CA certificate used to verify that the switch is connected to a trustworthy controller.", - "type": "string"}}}}}