[ovs-dev] [PATCH v3 6/8] ovsdb-client: Add new "restore" command.

Ben Pfaff blp at ovn.org
Wed Dec 13 23:10:17 UTC 2017


Signed-off-by: Ben Pfaff <blp at ovn.org>
---
 Documentation/ref/ovsdb.7.rst |  11 +++--
 NEWS                          |   2 +-
 lib/ovsdb-data.c              |  76 ++++++++++++++++++++++++-----
 lib/ovsdb-data.h              |   5 ++
 lib/ovsdb-idl.c               |  20 +-------
 ovsdb/ovsdb-client.1.in       |  28 ++++++++++-
 ovsdb/ovsdb-client.c          | 108 ++++++++++++++++++++++++++++++++++++++++++
 tests/ovsdb-client.at         |  58 ++++++++++++++++++++++-
 8 files changed, 271 insertions(+), 37 deletions(-)

diff --git a/Documentation/ref/ovsdb.7.rst b/Documentation/ref/ovsdb.7.rst
index ddc573a47c94..1f860768df2d 100644
--- a/Documentation/ref/ovsdb.7.rst
+++ b/Documentation/ref/ovsdb.7.rst
@@ -317,9 +317,14 @@ Another way to make a backup is to use ``ovsdb-client backup``, which
 connects to a running database server and outputs an atomic snapshot of its
 schema and content, in the same format used for on-disk databases.
 
-To restore from a backup, stop the database server or servers, overwrite
-the database file with the backup (e.g. with ``cp``), and then
-restart the servers.
+Multiple options are also available when the time comes to restore a database
+from a backup.  One option is to stop the database server or servers, overwrite
+the database file with the backup (e.g. with ``cp``), and then restart the
+servers.  Another way is to use ``ovsdb-client restore``, which connects to a
+running database server and replaces the data in one of its databases by a
+provided snapshot.  Using ``ovsdb-client restore`` has the disadvantage that
+UUIDs of rows in the restored database will differ from those in the snapshot,
+because the OVSDB protocol does not allow clients to specify row UUIDs.
 
 None of these approaches saves and restores data in columns that the schema
 designates as ephemeral.  This is by design: the designer of a schema only
diff --git a/NEWS b/NEWS
index 752e98f073e1..85bae499b25e 100644
--- a/NEWS
+++ b/NEWS
@@ -6,7 +6,7 @@ Post-v2.8.0
      * New file format documentation for developers in ovsdb(5).
      * Protocol documentation moved from ovsdb-server(1) to ovsdb-server(7).
      * ovsdb-client: New "get-schema-cksum" command.
-     * ovsdb-client: New "backup" command.
+     * ovsdb-client: New "backup" and "restore" commands.
    - OVN:
      * The "requested-chassis" option for a logical switch port now accepts a
        chassis "hostname" in addition to a chassis "name".
diff --git a/lib/ovsdb-data.c b/lib/ovsdb-data.c
index 3ddf5f5bd539..cdd1bb6530da 100644
--- a/lib/ovsdb-data.c
+++ b/lib/ovsdb-data.c
@@ -1341,15 +1341,26 @@ ovsdb_transient_datum_from_json(struct ovsdb_datum *datum,
     return ovsdb_datum_from_json(datum, &relaxed_type, json, NULL);
 }
 
-/* Converts 'datum', of the specified 'type', to JSON format, and returns the
- * JSON.  The caller is responsible for freeing the returned JSON.
- *
- * 'type' constraints on datum->n are ignored.
- *
- * Refer to RFC 7047 for the format of the JSON that this function produces. */
-struct json *
-ovsdb_datum_to_json(const struct ovsdb_datum *datum,
-                    const struct ovsdb_type *type)
+static struct json *
+ovsdb_base_to_json(const union ovsdb_atom *atom,
+                   const struct ovsdb_base_type *base,
+                   bool use_row_names)
+{
+    if (!use_row_names
+        || base->type != OVSDB_TYPE_UUID
+        || !base->u.uuid.refTableName) {
+        return ovsdb_atom_to_json(atom, base->type);
+    } else {
+        return json_array_create_2(
+            json_string_create("named-uuid"),
+            json_string_create_nocopy(ovsdb_data_row_name(&atom->uuid)));
+    }
+}
+
+static struct json *
+ovsdb_datum_to_json__(const struct ovsdb_datum *datum,
+                      const struct ovsdb_type *type,
+                      bool use_row_names)
 {
     if (ovsdb_type_is_map(type)) {
         struct json **elems;
@@ -1358,26 +1369,49 @@ ovsdb_datum_to_json(const struct ovsdb_datum *datum,
         elems = xmalloc(datum->n * sizeof *elems);
         for (i = 0; i < datum->n; i++) {
             elems[i] = json_array_create_2(
-                ovsdb_atom_to_json(&datum->keys[i], type->key.type),
-                ovsdb_atom_to_json(&datum->values[i], type->value.type));
+                ovsdb_base_to_json(&datum->keys[i], &type->key,
+                                   use_row_names),
+                ovsdb_base_to_json(&datum->values[i], &type->value,
+                                   use_row_names));
         }
 
         return wrap_json("map", json_array_create(elems, datum->n));
     } else if (datum->n == 1) {
-        return ovsdb_atom_to_json(&datum->keys[0], type->key.type);
+        return ovsdb_base_to_json(&datum->keys[0], &type->key, use_row_names);
     } else {
         struct json **elems;
         size_t i;
 
         elems = xmalloc(datum->n * sizeof *elems);
         for (i = 0; i < datum->n; i++) {
-            elems[i] = ovsdb_atom_to_json(&datum->keys[i], type->key.type);
+            elems[i] = ovsdb_base_to_json(&datum->keys[i], &type->key,
+                                          use_row_names);
         }
 
         return wrap_json("set", json_array_create(elems, datum->n));
     }
 }
 
+/* Converts 'datum', of the specified 'type', to JSON format, and returns the
+ * JSON.  The caller is responsible for freeing the returned JSON.
+ *
+ * 'type' constraints on datum->n are ignored.
+ *
+ * Refer to RFC 7047 for the format of the JSON that this function produces. */
+struct json *
+ovsdb_datum_to_json(const struct ovsdb_datum *datum,
+                    const struct ovsdb_type *type)
+{
+    return ovsdb_datum_to_json__(datum, type, false);
+}
+
+struct json *
+ovsdb_datum_to_json_with_row_names(const struct ovsdb_datum *datum,
+                                   const struct ovsdb_type *type)
+{
+    return ovsdb_datum_to_json__(datum, type, true);
+}
+
 static const char *
 skip_spaces(const char *p)
 {
@@ -2150,3 +2184,19 @@ ovsdb_atom_range_check_size(int64_t range_start, int64_t range_end)
     }
     return NULL;
 }
+
+char *
+ovsdb_data_row_name(const struct uuid *uuid)
+{
+    char *name;
+    char *p;
+
+    name = xasprintf("row"UUID_FMT, UUID_ARGS(uuid));
+    for (p = name; *p != '\0'; p++) {
+        if (*p == '-') {
+            *p = '_';
+        }
+    }
+
+    return name;
+}
diff --git a/lib/ovsdb-data.h b/lib/ovsdb-data.h
index c7bb095cd93a..84639c4a3ecf 100644
--- a/lib/ovsdb-data.h
+++ b/lib/ovsdb-data.h
@@ -244,6 +244,11 @@ void ovsdb_datum_add_unsafe(struct ovsdb_datum *,
                             const struct ovsdb_type *,
                             const union ovsdb_atom *range_end_atom);
 
+/* Transactions with named-uuid row names. */
+struct json *ovsdb_datum_to_json_with_row_names(const struct ovsdb_datum *,
+                                                const struct ovsdb_type *);
+char *ovsdb_data_row_name(const struct uuid *);
+
 /* Type checking. */
 static inline bool
 ovsdb_datum_conforms_to_type(const struct ovsdb_datum *datum,
diff --git a/lib/ovsdb-idl.c b/lib/ovsdb-idl.c
index 96e5c1f58bbf..29f893116aee 100644
--- a/lib/ovsdb-idl.c
+++ b/lib/ovsdb-idl.c
@@ -3102,22 +3102,6 @@ where_uuid_equals(const struct uuid *uuid)
                         xasprintf(UUID_FMT, UUID_ARGS(uuid))))));
 }
 
-static char *
-uuid_name_from_uuid(const struct uuid *uuid)
-{
-    char *name;
-    char *p;
-
-    name = xasprintf("row"UUID_FMT, UUID_ARGS(uuid));
-    for (p = name; *p != '\0'; p++) {
-        if (*p == '-') {
-            *p = '_';
-        }
-    }
-
-    return name;
-}
-
 static const struct ovsdb_idl_row *
 ovsdb_idl_txn_get_row(const struct ovsdb_idl_txn *txn, const struct uuid *uuid)
 {
@@ -3152,7 +3136,7 @@ substitute_uuids(struct json *json, const struct ovsdb_idl_txn *txn)
 
                 return json_array_create_2(
                     json_string_create("named-uuid"),
-                    json_string_create_nocopy(uuid_name_from_uuid(&uuid)));
+                    json_string_create_nocopy(ovsdb_data_row_name(&uuid)));
             }
         }
 
@@ -3567,7 +3551,7 @@ ovsdb_idl_txn_commit(struct ovsdb_idl_txn *txn)
 
                 json_object_put(op, "uuid-name",
                                 json_string_create_nocopy(
-                                    uuid_name_from_uuid(&row->uuid)));
+                                    ovsdb_data_row_name(&row->uuid)));
 
                 insert = xmalloc(sizeof *insert);
                 insert->dummy = row->uuid;
diff --git a/ovsdb/ovsdb-client.1.in b/ovsdb/ovsdb-client.1.in
index 2e2df5e5aa7f..30de9c536600 100644
--- a/ovsdb/ovsdb-client.1.in
+++ b/ovsdb/ovsdb-client.1.in
@@ -33,6 +33,9 @@ ovsdb\-client \- command-line interface to \fBovsdb-server\fR(1)
 \fBovsdb\-client \fR[\fIoptions\fR]
 \fBbackup\fI \fR[\fIserver\fR] \fR[\fIdatabase\fR] > \fIsnapshot\fR
 .br
+\fBovsdb\-client \fR[\fIoptions\fR] [\fB\-\-force\fR]
+\fBrestore\fI \fR[\fIserver\fR] \fR[\fIdatabase\fR] < \fIsnapshot\fR
+.br
 \fBovsdb\-client \fR[\fIoptions\fR] \fBmonitor\fI \fR[\fIserver\fR] \fR[\fIdatabase\fR] \fItable\fR
 [\fIcolumn\fR[\fB,\fIcolumn\fR]...]...
 .br
@@ -41,7 +44,6 @@ ovsdb\-client \- command-line interface to \fBovsdb-server\fR(1)
 \fBovsdb\-client \fR[\fIoptions\fR] \fBmonitor\-cond\fI \fR[\fIserver\fR] \fR[\fIdatabase\fR] \fIconditions
 \fItable\fR [\fIcolumn\fR[\fB,\fIcolumn\fR]...]...
 .IP "Testing Commands:"
-.br
 \fBovsdb\-client \fR[\fIoptions\fR] \fBlock\fI \fR[\fIserver\fR] \fIlock\fR
 .br
 \fBovsdb\-client \fR[\fIoptions\fR] \fBsteal\fI \fR[\fIserver\fR] \fIlock\fR
@@ -156,6 +158,30 @@ database is in use.
 The output does not include ephemeral columns, which by design do not
 survive across restarts of \fBovsdb\-server\fR.
 .
+.IP "[\fB\-\-force\fR] \fBrestore\fI \fR[\fIserver\fR] \fR[\fIdatabase\fR] < \fIsnapshot\fR"
+Reads \fIsnapshot\fR, which must be in the format used for OVSDB
+standalone and active-backup databases.  Then, connects to
+\fIserver\fR, verifies that \fIdatabase\fR and \fIsnapshot\fR have the
+same schema, then deletes all of the data in \fIdatabase\fR and
+replaces it by \fIsnapshot\fR.  The replacement happens atomically, in a
+single transaction.
+.IP
+UUIDs for rows in the restored database will differ from those in
+\fIsnapshot\fR, because the OVSDB protocol does not allow clients to
+specify row UUIDs.  Another way to restore a database,
+which does also restore row UUIDs, is to stop
+the server or servers, replace the database file by the snapshot, then
+restart the database.  Either way, ephemeral columns are not restored,
+since by design they do not survive across restarts of
+\fBovsdb\-server\fR.
+.IP
+Normally \fBrestore\fR exits with a failure if \fBsnapshot\fR and the
+server's database have different schemas.  In such a case, it is a
+good idea to convert the database to the new schema before restoring,
+e.g. with \fBovsdb\-client convert\fR.  Use \fB\-\-force\fR to proceed
+regardless of schema differences even though the restore might fail
+with an error or succeed with surprising results.
+.
 .IP "\fBmonitor\fI \fR[\fIserver\fR] \fR[\fIdatabase\fR] \fItable\fR [\fIcolumn\fR[\fB,\fIcolumn\fR]...]..."
 .IQ "\fBmonitor\-cond\fI \fR[\fIserver\fR] \fR[\fIdatabase\fR] \fIconditions\fR \fItable\fR [\fIcolumn\fR[\fB,\fIcolumn\fR]...]..."
 Connects to \fIserver\fR and monitors the contents of rows that match conditions in
diff --git a/ovsdb/ovsdb-client.c b/ovsdb/ovsdb-client.c
index 568c46b84d54..1af19d5dbc28 100644
--- a/ovsdb/ovsdb-client.c
+++ b/ovsdb/ovsdb-client.c
@@ -41,6 +41,7 @@
 #include "ovsdb-data.h"
 #include "ovsdb-error.h"
 #include "openvswitch/poll-loop.h"
+#include "row.h"
 #include "sort.h"
 #include "svec.h"
 #include "stream.h"
@@ -73,6 +74,9 @@ struct ovsdb_client_command {
 /* --timestamp: Print a timestamp before each update on "monitor" command? */
 static bool timestamp;
 
+/* --force: Ignore schema differences for "restore" command? */
+static bool force;
+
 /* Format for table output. */
 static struct table_style table_style = TABLE_STYLE_DEFAULT;
 
@@ -175,6 +179,7 @@ parse_options(int argc, char *argv[])
     enum {
         OPT_BOOTSTRAP_CA_CERT = UCHAR_MAX + 1,
         OPT_TIMESTAMP,
+        OPT_FORCE,
         VLOG_OPTION_ENUMS,
         DAEMON_OPTION_ENUMS,
         TABLE_OPTION_ENUMS,
@@ -184,6 +189,7 @@ parse_options(int argc, char *argv[])
         {"help", no_argument, NULL, 'h'},
         {"version", no_argument, NULL, 'V'},
         {"timestamp", no_argument, NULL, OPT_TIMESTAMP},
+        {"force", no_argument, NULL, OPT_FORCE},
         VLOG_LONG_OPTIONS,
         DAEMON_LONG_OPTIONS,
 #ifdef HAVE_OPENSSL
@@ -226,6 +232,10 @@ parse_options(int argc, char *argv[])
             timestamp = true;
             break;
 
+        case OPT_FORCE:
+            force = true;
+            break;
+
         case '?':
             exit(EXIT_FAILURE);
 
@@ -279,6 +289,8 @@ usage(void)
            "    dump contents of DATABASE on SERVER to stdout\n"
            "\n  backup [SERVER] [DATABASE] > DB\n"
            "    dump database contents in the form of a database file\n"
+           "\n  [--force] restore [SERVER] [DATABASE] < DB\n"
+           "    restore database contents from a database file\n"
            "\n  lock [SERVER] LOCK\n"
            "    create or wait for LOCK in SERVER\n"
            "\n  steal [SERVER] LOCK\n"
@@ -1516,6 +1528,101 @@ do_backup(struct jsonrpc *rpc, const char *database,
 }
 
 static void
+do_restore(struct jsonrpc *rpc, const char *database,
+           int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+{
+    if (isatty(STDIN_FILENO)) {
+        ovs_fatal(0, "not reading backup from a terminal; "
+                  "please redirect stdin from a file");
+    }
+
+    struct ovsdb *backup;
+    check_ovsdb_error(ovsdb_file_open("/dev/stdin", true, &backup, NULL));
+
+    const struct ovsdb_schema *schema = backup->schema;
+    struct ovsdb_schema *schema2 = fetch_schema(rpc, database);
+    if (!ovsdb_schema_equal(schema, schema2)) {
+        struct ds s = DS_EMPTY_INITIALIZER;
+        if (strcmp(schema->version, schema2->version)) {
+            ds_put_format(&s, "backup schema has version \"%s\" but "
+                          "database schema has version \"%s\"",
+                          schema->version, schema2->version);
+        } else {
+            ds_put_format(&s, "backup schema and database schema are "
+                          "both version %s but still differ",
+                          schema->version);
+        }
+        if (!force) {
+            ovs_fatal(0, "%s (use --force to override differences, or "
+                      "\"ovsdb-client convert\" to change the schema)",
+                      ds_cstr(&s));
+        }
+        VLOG_INFO("%s", ds_cstr(&s));
+        ds_destroy(&s);
+    }
+
+    struct json *txn = json_array_create_empty();
+    json_array_add(txn, json_string_create(schema->name));
+    struct shash_node *node;
+    SHASH_FOR_EACH (node, &backup->tables) {
+        const char *table_name = node->name;
+        struct ovsdb_table *table = node->data;
+
+        struct json *del_op = json_object_create();
+        json_object_put_string(del_op, "op", "delete");
+        json_object_put_string(del_op, "table", table_name);
+        json_object_put(del_op, "where", json_array_create_empty());
+        json_array_add(txn, del_op);
+
+        const struct ovsdb_row *row;
+        HMAP_FOR_EACH (row, hmap_node, &table->rows) {
+            struct json *ins_op = json_object_create();
+            json_object_put_string(ins_op, "op", "insert");
+            json_object_put_string(ins_op, "table", table_name);
+            json_object_put(ins_op, "uuid-name",
+                            json_string_create_nocopy(
+                                ovsdb_data_row_name(ovsdb_row_get_uuid(row))));
+            struct json *row_json = json_object_create();
+            json_object_put(ins_op, "row", row_json);
+
+            struct shash_node *node2;
+            SHASH_FOR_EACH (node2, &table->schema->columns) {
+                const struct ovsdb_column *column = node2->data;
+                const struct ovsdb_datum *datum = &row->fields[column->index];
+                const struct ovsdb_type *type = &column->type;
+                if (column->persistent
+                    && column->index >= OVSDB_N_STD_COLUMNS
+                    && !ovsdb_datum_is_default(datum, type)) {
+                    struct json *value = ovsdb_datum_to_json_with_row_names(
+                        datum, type);
+                    json_object_put(row_json, column->name, value);
+                }
+            }
+            json_array_add(txn, ins_op);
+        }
+    }
+    struct jsonrpc_msg *rq = jsonrpc_create_request("transact", txn, NULL);
+    struct jsonrpc_msg *reply;
+    check_txn(jsonrpc_transact_block(rpc, rq, &reply), &reply);
+    if (reply->result->type != JSON_ARRAY) {
+        ovs_fatal(0, "result is not array");
+    }
+    for (size_t i = 0; i < json_array(reply->result)->n; i++) {
+        struct json *json = json_array(reply->result)->elems[i];
+        if (json->type != JSON_OBJECT) {
+            ovs_fatal(0, "result array element is not object");
+        }
+        struct shash *object = json_object(json);
+        if (shash_find(object, "error")) {
+            ovs_fatal(0, "server returned error reply: %s",
+                      json_to_string(json, JSSF_SORT));
+        }
+    }
+    jsonrpc_msg_destroy(reply);
+}
+
+
+static void
 do_help(struct jsonrpc *rpc OVS_UNUSED, const char *database OVS_UNUSED,
         int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
 {
@@ -1728,6 +1835,7 @@ static const struct ovsdb_client_command all_commands[] = {
     { "monitor-cond",       NEED_DATABASE, 2, 3,       do_monitor_cond },
     { "dump",               NEED_DATABASE, 0, INT_MAX, do_dump },
     { "backup",             NEED_DATABASE, 0, 0,       do_backup },
+    { "restore",            NEED_DATABASE, 0, 0,       do_restore },
     { "lock",               NEED_RPC,      1, 1,       do_lock_create },
     { "steal",              NEED_RPC,      1, 1,       do_lock_steal },
     { "unlock",             NEED_RPC,      1, 1,       do_lock_unlock },
diff --git a/tests/ovsdb-client.at b/tests/ovsdb-client.at
index b35e9f75e972..0aa20df576d6 100644
--- a/tests/ovsdb-client.at
+++ b/tests/ovsdb-client.at
@@ -12,7 +12,7 @@ AT_CHECK([ovsdb-client get-schema-cksum unix:socket ordinals], [0], [12345678 9
 OVSDB_SERVER_SHUTDOWN
 AT_CLEANUP
 
-AT_SETUP([ovsdb-client backup])
+AT_SETUP([ovsdb-client backup and restore])
 AT_KEYWORDS([ovsdb client positive])
 
 on_exit 'kill `cat *.pid`'
@@ -50,6 +50,62 @@ dnl Dump a copy of the data and a backup of it.
 AT_CHECK([ovsdb-client dump > dump1])
 AT_CHECK([ovsdb-client backup > backup])
 
+dnl Mess up the data a little, verify that it changed, then restore it
+dnl and verify restoration.
+AT_CHECK(
+  [[ovsdb-client transact '
+      ["ordinals",
+       {"op": "update",
+        "table": "ordinals",
+	"where": [],
+	"row": {"name": ""}}]']],
+  [0],
+  [[[{"count":6}]
+]])
+AT_CHECK([ovsdb-client --no-headings dump ordinals | sort -k 3 | uuidfilt], [0], [dnl
+ordinals table
+<0> ""   0
+<1> ""   1
+<2> ""   2
+<3> ""   3
+<4> ""   4
+<5> ""   5
+])
+AT_CHECK([ovsdb-client restore < backup])
+AT_CHECK([ovsdb-client dump | tr -s ' ' | sort -k 3 | uuidfilt], [0], [dnl
+ordinals table
+------------------------------------ ----- ------
+<0> zero 0
+<1> one 1
+<2> two 2
+<3> three 3
+<4> four 4
+<5> five 5
+_uuid name number
+])
+# Combining the original dump and the backup dump should reveal that the
+# rows have different uuids:
+AT_CHECK([(ovsdb-client dump; cat dump1) | tr -s ' ' | sort -k 3 | uuidfilt], [0], [dnl
+ordinals table
+ordinals table
+------------------------------------ ----- ------
+------------------------------------ ----- ------
+<0> zero 0
+<1> zero 0
+<2> one 1
+<3> one 1
+<4> two 2
+<5> two 2
+<6> three 3
+<7> three 3
+<8> four 4
+<9> four 4
+<10> five 5
+<11> five 5
+_uuid name number
+_uuid name number
+])
+
 dnl Stop the database server, then re-start it based on the backup.
 OVS_APP_EXIT_AND_WAIT([ovsdb-server])
 AT_CHECK([ovsdb-server -vfile -vvlog:off --detach --no-chdir --pidfile --log-file --remote=punix:db.sock backup], [0])
-- 
2.10.2



More information about the dev mailing list