diff options
Diffstat (limited to 'ovsdb')
-rw-r--r-- | ovsdb/SPECS | 628 | ||||
-rw-r--r-- | ovsdb/automake.mk | 44 | ||||
-rw-r--r-- | ovsdb/column.c | 232 | ||||
-rw-r--r-- | ovsdb/column.h | 84 | ||||
-rw-r--r-- | ovsdb/condition.c | 284 | ||||
-rw-r--r-- | ovsdb/condition.h | 72 | ||||
-rw-r--r-- | ovsdb/execution.c | 613 | ||||
-rw-r--r-- | ovsdb/file.c | 360 | ||||
-rw-r--r-- | ovsdb/file.h | 36 | ||||
-rw-r--r-- | ovsdb/jsonrpc-server.c | 362 | ||||
-rw-r--r-- | ovsdb/jsonrpc-server.h | 29 | ||||
-rw-r--r-- | ovsdb/ovsdb-server.c | 223 | ||||
-rw-r--r-- | ovsdb/ovsdb-tool.c | 204 | ||||
-rw-r--r-- | ovsdb/ovsdb.c | 262 | ||||
-rw-r--r-- | ovsdb/ovsdb.h | 73 | ||||
-rw-r--r-- | ovsdb/query.c | 99 | ||||
-rw-r--r-- | ovsdb/query.h | 37 | ||||
-rw-r--r-- | ovsdb/row.c | 386 | ||||
-rw-r--r-- | ovsdb/row.h | 139 | ||||
-rw-r--r-- | ovsdb/table.c | 228 | ||||
-rw-r--r-- | ovsdb/table.h | 63 | ||||
-rw-r--r-- | ovsdb/transaction.c | 444 | ||||
-rw-r--r-- | ovsdb/transaction.h | 41 | ||||
-rw-r--r-- | ovsdb/trigger.c | 129 | ||||
-rw-r--r-- | ovsdb/trigger.h | 44 |
25 files changed, 5116 insertions, 0 deletions
diff --git a/ovsdb/SPECS b/ovsdb/SPECS new file mode 100644 index 000000000..12d97682a --- /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. + +<string> + + A JSON string. + +<id> + + A JSON string matching [a-zA-Z_][a-zA-Z0-9_]*. + + <id>s that begin with _ are reserved to the implementation and may + not be used by the user. + +<boolean> + + A JSON true or false value. + +<number> + + A JSON number. + +<integer> + + 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 <database-schema>, as described below. + +<database-schema> + + A JSON object with the following members: + + "name": <id> required + "comment": <string> optional + "tables": {<id>: <table-schema>, ...} 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 <table-schema>s. + +<table-schema> + + A JSON object with the following members: + + "comment": <string> optional + "columns": {<id>: <column-schema>, ...} 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 <column-schema>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. + +<column-schema> + + A JSON object with the following members: + + "comment": <string> optional + "type": <type> required + "ephemeral": <boolean> 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. + +<type> + + The type of a database column. Either an <atomic-type> or a JSON + object that describes the type of a database column, with the + following members: + + "key": <atomic-type> required + "value": <atomic-type> optional + "min": <integer> optional + "max": <integer> 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". + +<atomic-type> + + 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": <database-schema> + "error": null + "id": same "id" as request + +This operation retrieves a <database-schema> that describes the +hosted database. + +transact +........ + +Request object members: + + "method": "transact" required + "params": [<operation>*] required + "id": any JSON value except null required + +Response object members: + + "result": [<object>*] + "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: + + <no response> + +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 +------------------------------ + +<table> + + An <id> that names a table. + +<column> + + An <id> that names a table column. + +<row> + + 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 + <value> of that column. + +<value> + + A JSON value that represents the value of a column in a table row, + one of <atom>, a <set>, or a <map>. + +<atom> + + A JSON value that represents a scalar value for a column, one of + <string>, <number>, <boolean>, <uuid>, <named-uuid>. + +<set> + + 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 <atom>s giving the values + in the set. All of the <atom>s must have the same type. + +<map> + + 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 <pair>s giving the values + in the map. All of the <pair>s must have the same key and value + types. + + (JSON objects are not used to represent <map> because JSON only + allows string names in an object.) + +<pair> + + A 2-element JSON array that represents a pair within a database + map. The first element is an <atom> that represents the key, the + second element is an <atom> that represents the value. + +<uuid> + + 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 <uuid> represents the + UUID 550e8400-e29b-41d4-a716-446655440000: + + ["uuid", "550e8400-e29b-41d4-a716-446655440000"] + +<named-uuid> + + 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 + <named-uuid> represents the UUID created by that operation: + + ["named-uuid", "myrow"] + +<condition> + + A 3-element JSON array of the form [<column>, <function>, + <value>] that represents a test on a column value. + + Except as otherwise specified below, <value> must have the same + type as <column>. + + The meaning depends on the type of <column>: + + integer + real + + <function> must be "<", "<=", "==", "!=", ">=", ">", + "includes", or "excludes". + + The test is true if the column's value satisfies the + relation <function> <value>, e.g. if the column has value + 1 and <value> is 2, the test is true if <function> is "<", + "<=" or "!=", but not otherwise. + + "includes" is equivalent to "=="; "excludes" is equivalent + to "!=". + + boolean + string + uuid + + <function> must be "!=", "==", "includes", or "excludes". + + If <function> is "==" or "includes", the test is true if + the column's value equals <value>. If <function> is "!=" + or "excludes", the test is inverted. + + set + map + + <function> must be "!=", "==", "includes", or "excludes". + + If <function> is "==", the test is true if the column's + value contains exactly the same values (for sets) or pairs + (for maps). If <function> is "!=", the test is inverted. + + If <function> is "includes", the test is true if the + column's value contains all of the values (for sets) or + pairs (for maps) in <value>. The column's value may also + contain other values or pairs. + + If <function> 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 <value>. The column's value + may contain other values or pairs not in <value>. + + If <function> is "includes" or "excludes", then the + required type of <value> is slightly relaxed, in that it + may have fewer than the minimum number of elements + specified by the column's type. If <function> is + "excludes", then the required type is additionally relaxed + in that <value> may have more than the maximum number of + elements specified by the column's type. + +<function> + + One of "<", "<=", "==", "!=", ">=", ">", "includes", "excludes". + +Operations +---------- + +Each of the available operations is described below. + +insert +...... + +Request object members: + + "op": "insert" required + "table": <table> required + "row": <row> required + "uuid-name": <string> optional + +Result object members: + + "uuid": <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": <table> required + "where": [<condition>*] required + "columns": [<column>*] optional + +Result object members: + + "rows": [<row>*] + +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": <table> required + "where": [<condition>*] required + "row": <row> required + +Result object members: + + "count": <integer> + +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": <table> required + "where": [<condition>*] required + +Result object members: + + "count": <integer> + +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": <integer> optional + "table": <table> required + "where": [<condition>*] required + "columns": [<column>*] required + "until": "==" or "!=" required + "rows": [<row>*] 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": <boolean> 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 000000000..d2a3e04ce --- /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 000000000..1e8a2d09d --- /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 <config.h> + +#include "ovsdb/column.h" + +#include <stdlib.h> + +#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 000000000..594215108 --- /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 <stdbool.h> +#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 000000000..0342b8e89 --- /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 <config.h> + +#include "condition.h" + +#include <limits.h> + +#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 000000000..8c422b957 --- /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 <stddef.h> +#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 000000000..25b34b196 --- /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 <config.h> + +#include <assert.h> +#include <limits.h> + +#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 000000000..4883d76f1 --- /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 <config.h> + +#include "file.h" + +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#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 000000000..5178140a1 --- /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 <sys/types.h> +#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 000000000..36c9a7a26 --- /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 <config.h> + +#include "jsonrpc-server.h" + +#include <errno.h> + +#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 000000000..49b5f8a96 --- /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 000000000..17a9970ea --- /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 <config.h> + +#include "ovsdb.h" + +#include <errno.h> +#include <getopt.h> +#include <signal.h> + +#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 000000000..5169653d8 --- /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 <config.h> +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <signal.h> +#include <stdlib.h> +#include <string.h> + +#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 000000000..e653758fd --- /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 <config.h> + +#include "ovsdb.h" + +#include <fcntl.h> + +#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 000000000..3f62966a7 --- /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 000000000..878ac5b2d --- /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 <config.h> + +#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 == + * <some-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 000000000..f5cfe2e64 --- /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 <stdbool.h> + +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 000000000..1b8194201 --- /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 <config.h> + +#include "row.h" + +#include <assert.h> +#include <stddef.h> + +#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 000000000..55c4f1426 --- /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 <stddef.h> +#include <stdint.h> +#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 000000000..d017a6ba0 --- /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 <config.h> + +#include "table.h" + +#include <assert.h> + +#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 000000000..9e36c911d --- /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 <stdbool.h> +#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 000000000..21a46ec7c --- /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 <config.h> + +#include "transaction.h" + +#include <assert.h> + +#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 000000000..293eaf4ff --- /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 <stdbool.h> +#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 000000000..1ecfdcac1 --- /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 <config.h> + +#include "trigger.h" + +#include <assert.h> +#include <limits.h> + +#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 000000000..521b150b1 --- /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 */ |