Prepare Open vSwitch 1.1.2 release.
[sliver-openvswitch.git] / lib / ovsdb-idl.c
index cffc5dc..fd15ea9 100644 (file)
@@ -1,4 +1,4 @@
-/* Copyright (c) 2009, 2010 Nicira Networks.
+/* Copyright (c) 2009, 2010, 2011 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -36,7 +36,7 @@
 #include "util.h"
 #include "vlog.h"
 
-VLOG_DEFINE_THIS_MODULE(ovsdb_idl)
+VLOG_DEFINE_THIS_MODULE(ovsdb_idl);
 
 /* An arc from one idl_row to another.  When row A contains a UUID that
  * references row B, this is represented by an arc from A (the source) to B
@@ -66,7 +66,7 @@ struct ovsdb_idl {
     const struct ovsdb_idl_class *class;
     struct jsonrpc_session *session;
     struct shash table_by_name;
-    struct ovsdb_idl_table *tables;
+    struct ovsdb_idl_table *tables; /* Contains "struct ovsdb_idl_table *"s.*/
     struct json *monitor_request_id;
     unsigned int last_monitor_request_seqno;
     unsigned int change_seqno;
@@ -140,13 +140,29 @@ static bool ovsdb_idl_txn_process_reply(struct ovsdb_idl *,
  * form acceptable to jsonrpc_session_open().  The connection will maintain an
  * in-memory replica of the remote database whose schema is described by
  * 'class'.  (Ordinarily 'class' is compiled from an OVSDB schema automatically
- * by ovsdb-idlc.) */
+ * by ovsdb-idlc.)
+ *
+ * If 'monitor_everything_by_default' is true, then everything in the remote
+ * database will be replicated by default.  ovsdb_idl_omit() and
+ * ovsdb_idl_omit_alert() may be used to selectively drop some columns from
+ * monitoring.
+ *
+ * If 'monitor_everything_by_default' is false, then no columns or tables will
+ * be replicated by default.  ovsdb_idl_add_column() and ovsdb_idl_add_table()
+ * must be used to choose some columns or tables to replicate.
+ */
 struct ovsdb_idl *
-ovsdb_idl_create(const char *remote, const struct ovsdb_idl_class *class)
+ovsdb_idl_create(const char *remote, const struct ovsdb_idl_class *class,
+                 bool monitor_everything_by_default)
 {
     struct ovsdb_idl *idl;
+    uint8_t default_mode;
     size_t i;
 
+    default_mode = (monitor_everything_by_default
+                    ? OVSDB_IDL_MONITOR | OVSDB_IDL_ALERT
+                    : 0);
+
     idl = xzalloc(sizeof *idl);
     idl->class = class;
     idl->session = jsonrpc_session_open(remote);
@@ -160,7 +176,8 @@ ovsdb_idl_create(const char *remote, const struct ovsdb_idl_class *class)
         shash_add_assert(&idl->table_by_name, tc->name, table);
         table->class = tc;
         table->modes = xmalloc(tc->n_columns);
-        memset(table->modes, OVSDB_IDL_MODE_RW, tc->n_columns);
+        memset(table->modes, default_mode, tc->n_columns);
+        table->need_table = false;
         shash_init(&table->columns);
         for (j = 0; j < tc->n_columns; j++) {
             const struct ovsdb_idl_column *column = &tc->columns[j];
@@ -265,7 +282,7 @@ ovsdb_idl_run(struct ovsdb_idl *idl)
     assert(!idl->txn);
     jsonrpc_session_run(idl->session);
     for (i = 0; jsonrpc_session_is_connected(idl->session) && i < 50; i++) {
-        struct jsonrpc_msg *msg, *reply;
+        struct jsonrpc_msg *msg;
         unsigned int seqno;
 
         seqno = jsonrpc_session_get_seqno(idl->session);
@@ -281,7 +298,6 @@ ovsdb_idl_run(struct ovsdb_idl *idl)
             break;
         }
 
-        reply = NULL;
         if (msg->type == JSONRPC_NOTIFY
                    && !strcmp(msg->method, "update")
                    && msg->params->type == JSON_ARRAY
@@ -296,8 +312,7 @@ ovsdb_idl_run(struct ovsdb_idl *idl)
             idl->monitor_request_id = NULL;
             ovsdb_idl_clear(idl);
             ovsdb_idl_parse_update(idl, msg->result);
-        } else if (msg->type == JSONRPC_REPLY
-                   && msg->id && msg->id->type == JSON_STRING
+        } else if (msg->type == JSONRPC_REPLY && msg->id->type == JSON_STRING
                    && !strcmp(msg->id->u.string, "echo")) {
             /* It's a reply to our echo request.  Ignore it. */
         } else if ((msg->type == JSONRPC_ERROR
@@ -312,9 +327,6 @@ ovsdb_idl_run(struct ovsdb_idl *idl)
                      jsonrpc_session_get_name(idl->session),
                      jsonrpc_msg_type_to_string(msg->type));
         }
-        if (reply) {
-            jsonrpc_session_send(idl->session, reply);
-        }
         jsonrpc_msg_destroy(msg);
     }
 
@@ -359,64 +371,113 @@ ovsdb_idl_force_reconnect(struct ovsdb_idl *idl)
 {
     jsonrpc_session_force_reconnect(idl->session);
 }
-
-static void
-ovsdb_idl_set_mode(struct ovsdb_idl *idl,
-                   const struct ovsdb_idl_column *column,
-                   enum ovsdb_idl_mode mode)
+\f
+static unsigned char *
+ovsdb_idl_get_mode(struct ovsdb_idl *idl,
+                   const struct ovsdb_idl_column *column)
 {
     size_t i;
 
+    assert(!idl->change_seqno);
+
     for (i = 0; i < idl->class->n_tables; i++) {
         const struct ovsdb_idl_table *table = &idl->tables[i];
         const struct ovsdb_idl_table_class *tc = table->class;
 
         if (column >= tc->columns && column < &tc->columns[tc->n_columns]) {
-            unsigned char *modep = &table->modes[column - tc->columns];
-            assert(*modep == OVSDB_IDL_MODE_RW || *modep == mode);
-            *modep = mode;
-            return;
+            return &table->modes[column - tc->columns];
         }
     }
 
     NOT_REACHED();
 }
 
-/* By default, 'idl' replicates all of the columns in the remote database, and
- * ovsdb_idl_run() returns true upon a change to any column in the database.
- * Call this function to avoid alerting ovsdb_idl_run()'s caller upon changes
- * to 'column'.
+static void
+add_ref_table(struct ovsdb_idl *idl, const struct ovsdb_base_type *base)
+{
+    if (base->type == OVSDB_TYPE_UUID && base->u.uuid.refTableName) {
+        struct ovsdb_idl_table *table;
+
+        table = shash_find_data(&idl->table_by_name,
+                                base->u.uuid.refTableName);
+        if (table) {
+            table->need_table = true;
+        } else {
+            VLOG_WARN("%s IDL class missing referenced table %s",
+                      idl->class->database, base->u.uuid.refTableName);
+        }
+    }
+}
+
+/* Turns on OVSDB_IDL_MONITOR and OVSDB_IDL_ALERT for 'column' in 'idl'.  Also
+ * ensures that any tables referenced by 'column' will be replicated, even if
+ * no columns in that table are selected for replication (see
+ * ovsdb_idl_add_table() for more information).
  *
- * This is useful for columns that a client treats as "write-only", that is, it
- * updates them but doesn't want to get alerted about its own updates.  It also
- * won't be alerted about other clients' updates, so this is suitable only for
- * use by a client that "owns" a particular column.
+ * This function is only useful if 'monitor_everything_by_default' was false in
+ * the call to ovsdb_idl_create().  This function should be called between
+ * ovsdb_idl_create() and the first call to ovsdb_idl_run().
+ */
+void
+ovsdb_idl_add_column(struct ovsdb_idl *idl,
+                     const struct ovsdb_idl_column *column)
+{
+    *ovsdb_idl_get_mode(idl, column) = OVSDB_IDL_MONITOR | OVSDB_IDL_ALERT;
+    add_ref_table(idl, &column->type.key);
+    add_ref_table(idl, &column->type.value);
+}
+
+/* Ensures that the table with class 'tc' will be replicated on 'idl' even if
+ * no columns are selected for replication.  This can be useful because it
+ * allows 'idl' to keep track of what rows in the table actually exist, which
+ * in turn allows columns that reference the table to have accurate contents.
+ * (The IDL presents the database with references to rows that do not exist
+ * removed.)
  *
- * The client must be careful not to retain pointers to data in 'column' across
- * calls to ovsdb_idl_run(), even when that function returns false, because
- * the client is not alerted to changes.
+ * This function is only useful if 'monitor_everything_by_default' was false in
+ * the call to ovsdb_idl_create().  This function should be called between
+ * ovsdb_idl_create() and the first call to ovsdb_idl_run().
+ */
+void
+ovsdb_idl_add_table(struct ovsdb_idl *idl,
+                    const struct ovsdb_idl_table_class *tc)
+{
+    size_t i;
+
+    for (i = 0; i < idl->class->n_tables; i++) {
+        struct ovsdb_idl_table *table = &idl->tables[i];
+
+        if (table->class == tc) {
+            table->need_table = true;
+            return;
+        }
+    }
+
+    NOT_REACHED();
+}
+
+/* Turns off OVSDB_IDL_ALERT for 'column' in 'idl'.
  *
- * This function should be called after ovsdb_idl_create(), but before the
- * first call to ovsdb_idl_run().  For any given column, this function may be
- * called or ovsdb_idl_omit() may be called, but not both. */
+ * This function should be called between ovsdb_idl_create() and the first call
+ * to ovsdb_idl_run().
+ */
 void
-ovsdb_idl_set_write_only(struct ovsdb_idl *idl,
-                         const struct ovsdb_idl_column *column)
+ovsdb_idl_omit_alert(struct ovsdb_idl *idl,
+                     const struct ovsdb_idl_column *column)
 {
-    ovsdb_idl_set_mode(idl, column, OVSDB_IDL_MODE_WO);
+    *ovsdb_idl_get_mode(idl, column) &= ~OVSDB_IDL_ALERT;
 }
 
-/* By default, 'idl' replicates all of the columns in the remote database.
- * Call this function to omit replicating 'column'.  This saves CPU time and
- * bandwidth to the database.
+/* Sets the mode for 'column' in 'idl' to 0.  See the big comment above
+ * OVSDB_IDL_MONITOR for details.
  *
- * This function should be called after ovsdb_idl_create(), but before the
- * first call to ovsdb_idl_run().  For any given column, this function may be
- * called or ovsdb_idl_set_write_only() may be called, but not both. */
+ * This function should be called between ovsdb_idl_create() and the first call
+ * to ovsdb_idl_run().
+ */
 void
 ovsdb_idl_omit(struct ovsdb_idl *idl, const struct ovsdb_idl_column *column)
 {
-    ovsdb_idl_set_mode(idl, column, OVSDB_IDL_MODE_NONE);
+    *ovsdb_idl_get_mode(idl, column) = 0;
 }
 \f
 static void
@@ -433,16 +494,22 @@ ovsdb_idl_send_monitor_request(struct ovsdb_idl *idl)
         struct json *monitor_request, *columns;
         size_t j;
 
-        monitor_request = json_object_create();
-        columns = json_array_create_empty();
+        columns = table->need_table ? json_array_create_empty() : NULL;
         for (j = 0; j < tc->n_columns; j++) {
             const struct ovsdb_idl_column *column = &tc->columns[j];
-            if (table->modes[j] != OVSDB_IDL_MODE_NONE) {
+            if (table->modes[j] & OVSDB_IDL_MONITOR) {
+                if (!columns) {
+                    columns = json_array_create_empty();
+                }
                 json_array_add(columns, json_string_create(column->name));
             }
         }
-        json_object_put(monitor_request, "columns", columns);
-        json_object_put(monitor_requests, tc->name, monitor_request);
+
+        if (columns) {
+            monitor_request = json_object_create();
+            json_object_put(monitor_request, "columns", columns);
+            json_object_put(monitor_requests, tc->name, monitor_request);
+        }
     }
 
     json_destroy(idl->monitor_request_id);
@@ -642,7 +709,7 @@ ovsdb_idl_row_update(struct ovsdb_idl_row *row, const struct json *row_json)
 
             if (!ovsdb_datum_equals(old, &datum, &column->type)) {
                 ovsdb_datum_swap(old, &datum);
-                if (table->modes[column_idx] == OVSDB_IDL_MODE_RW) {
+                if (table->modes[column_idx] & OVSDB_IDL_ALERT) {
                     changed = true;
                 }
             } else {
@@ -1041,6 +1108,15 @@ ovsdb_idl_get(const struct ovsdb_idl_row *row,
 
     return ovsdb_idl_read(row, column);
 }
+
+/* Returns false if 'row' was obtained from the IDL, true if it was initialized
+ * to all-zero-bits by some other entity.  If 'row' was set up some other way
+ * then the return value is indeterminate. */
+bool
+ovsdb_idl_row_is_synthetic(const struct ovsdb_idl_row *row)
+{
+    return row->table == NULL;
+}
 \f
 /* Transactions. */
 
@@ -1330,12 +1406,16 @@ ovsdb_idl_txn_commit(struct ovsdb_idl_txn *txn)
         if (row->old == row->new) {
             continue;
         } else if (!row->new) {
-            struct json *op = json_object_create();
-            json_object_put_string(op, "op", "delete");
-            json_object_put_string(op, "table", class->name);
-            json_object_put(op, "where", where_uuid_equals(&row->uuid));
-            json_array_add(operations, op);
-            any_updates = true;
+            if (class->is_root) {
+                struct json *op = json_object_create();
+                json_object_put_string(op, "op", "delete");
+                json_object_put_string(op, "table", class->name);
+                json_object_put(op, "where", where_uuid_equals(&row->uuid));
+                json_array_add(operations, op);
+                any_updates = true;
+            } else {
+                /* Let ovsdb-server decide whether to really delete it. */
+            }
         } else {
             struct json *row_json;
             struct json *op;
@@ -1369,9 +1449,7 @@ ovsdb_idl_txn_commit(struct ovsdb_idl_txn *txn)
                                                         &class->columns[idx];
 
                     if (row->old
-                        ? !ovsdb_datum_equals(&row->old[idx], &row->new[idx],
-                                              &column->type)
-                        : !ovsdb_datum_is_default(&row->new[idx],
+                        || !ovsdb_datum_is_default(&row->new[idx],
                                                   &column->type)) {
                         json_object_put(row_json, column->name,
                                         substitute_uuids(
@@ -1563,7 +1641,23 @@ ovsdb_idl_txn_write(const struct ovsdb_idl_row *row_,
 
     assert(row->new != NULL);
     assert(column_idx < class->n_columns);
-    assert(row->table->modes[column_idx] != OVSDB_IDL_MODE_NONE);
+    assert(row->old == NULL ||
+           row->table->modes[column_idx] & OVSDB_IDL_MONITOR);
+
+    /* If this is a write-only column and the datum being written is the same
+     * as the one already there, just skip the update entirely.  This is worth
+     * optimizing because we have a lot of columns that get periodically
+     * refreshed into the database but don't actually change that often.
+     *
+     * We don't do this for read/write columns because that would break
+     * atomicity of transactions--some other client might have written a
+     * different value in that column since we read it. */
+    if (row->table->modes[column_idx] == OVSDB_IDL_MONITOR
+        && ovsdb_datum_equals(ovsdb_idl_read(row, column),
+                              datum, &column->type)) {
+        ovsdb_datum_destroy(datum, &column->type);
+        return;
+    }
 
     if (hmap_node_is_null(&row->txn_node)) {
         hmap_insert(&row->table->idl->txn->txn_rows, &row->txn_node,
@@ -1620,6 +1714,8 @@ ovsdb_idl_txn_verify(const struct ovsdb_idl_row *row_,
     size_t column_idx = column - class->columns;
 
     assert(row->new != NULL);
+    assert(row->old == NULL ||
+           row->table->modes[column_idx] & OVSDB_IDL_MONITOR);
     if (!row->old
         || (row->written && bitmap_is_set(row->written, column_idx))) {
         return;