[ovs-dev] [PATCH RFC 49/52] ovsdb-client: Add new "restore" command.

Ben Pfaff blp at ovn.org
Tue Sep 19 22:01:22 UTC 2017


Signed-off-by: Ben Pfaff <blp at ovn.org>
---
 NEWS                    |   2 +-
 lib/ovsdb-data.c        |  76 +++++++++---
 lib/ovsdb-data.h        |   5 +
 lib/ovsdb-idl.c         |  20 +---
 manpages.mk             | 299 ++++++++++++++++++++++++++++++++++++++++++++++++
 ovsdb/log.c             |   8 +-
 ovsdb/ovsdb-client.1.in |  28 ++++-
 ovsdb/ovsdb-client.c    | 108 +++++++++++++++++
 ovsdb/ovsdb.7.xml       |  13 ++-
 9 files changed, 522 insertions(+), 37 deletions(-)

diff --git a/NEWS b/NEWS
index 686c387d782a..9782818a1b0d 100644
--- a/NEWS
+++ b/NEWS
@@ -8,7 +8,7 @@ Post-v2.8.0
      * ovsdb-server now always hosts a built-in database named _Server.  See
        ovsdb-server(5) for more details.
      * ovsdb-client: New "get-schema-cksum" command.
-     * ovsdb-client: New "backup" command.
+     * ovsdb-client: New "backup" and "restore" commands.
    - ovs-vsctl and other commands that display data in tables now support a
      --max-column-width option to limit column width.
    - OVN:
diff --git a/lib/ovsdb-data.c b/lib/ovsdb-data.c
index b4ea3dcac227..69122dc10432 100644
--- a/lib/ovsdb-data.c
+++ b/lib/ovsdb-data.c
@@ -1358,15 +1358,26 @@ ovsdb_unconstrained_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;
@@ -1375,26 +1386,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)
 {
@@ -2180,3 +2214,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 835d0a8a14e7..14716912ea49 100644
--- a/lib/ovsdb-data.h
+++ b/lib/ovsdb-data.h
@@ -251,6 +251,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 6cb1404735d1..949c15e11940 100644
--- a/lib/ovsdb-idl.c
+++ b/lib/ovsdb-idl.c
@@ -3178,22 +3178,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)
 {
@@ -3228,7 +3212,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)));
             }
         }
 
@@ -3643,7 +3627,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/manpages.mk b/manpages.mk
index e69de29bb2d1..7d6a507e0039 100644
--- a/manpages.mk
+++ b/manpages.mk
@@ -0,0 +1,299 @@
+# Generated automatically -- do not modify!    -*- buffer-read-only: t -*-
+
+ovn/utilities/ovn-detrace.1: \
+	ovn/utilities/ovn-detrace.1.in \
+	lib/common-syn.man \
+	lib/common.man
+ovn/utilities/ovn-detrace.1.in:
+lib/common-syn.man:
+lib/common.man:
+
+ovn/utilities/ovn-sbctl.8: \
+	ovn/utilities/ovn-sbctl.8.in \
+	lib/common.man \
+	lib/db-ctl-base.man \
+	lib/ssl-bootstrap.man \
+	lib/ssl-peer-ca-cert.man \
+	lib/ssl.man \
+	lib/table.man \
+	lib/vlog.man
+ovn/utilities/ovn-sbctl.8.in:
+lib/common.man:
+lib/db-ctl-base.man:
+lib/ssl-bootstrap.man:
+lib/ssl-peer-ca-cert.man:
+lib/ssl.man:
+lib/table.man:
+lib/vlog.man:
+
+ovsdb/ovsdb-client.1: \
+	ovsdb/ovsdb-client.1.in \
+	lib/common-syn.man \
+	lib/common.man \
+	lib/daemon-syn.man \
+	lib/daemon.man \
+	lib/ssl-bootstrap-syn.man \
+	lib/ssl-bootstrap.man \
+	lib/ssl-connect-syn.man \
+	lib/ssl-connect.man \
+	lib/ssl-syn.man \
+	lib/ssl.man \
+	lib/table.man \
+	lib/vlog-syn.man \
+	lib/vlog.man \
+	ovsdb/ovsdb-schemas.man
+ovsdb/ovsdb-client.1.in:
+lib/common-syn.man:
+lib/common.man:
+lib/daemon-syn.man:
+lib/daemon.man:
+lib/ssl-bootstrap-syn.man:
+lib/ssl-bootstrap.man:
+lib/ssl-connect-syn.man:
+lib/ssl-connect.man:
+lib/ssl-syn.man:
+lib/ssl.man:
+lib/table.man:
+lib/vlog-syn.man:
+lib/vlog.man:
+ovsdb/ovsdb-schemas.man:
+
+ovsdb/ovsdb-server.1: \
+	ovsdb/ovsdb-server.1.in \
+	lib/common-syn.man \
+	lib/common.man \
+	lib/coverage-unixctl.man \
+	lib/daemon-syn.man \
+	lib/daemon.man \
+	lib/memory-unixctl.man \
+	lib/service-syn.man \
+	lib/service.man \
+	lib/ssl-bootstrap-syn.man \
+	lib/ssl-bootstrap.man \
+	lib/ssl-connect-syn.man \
+	lib/ssl-connect.man \
+	lib/ssl-peer-ca-cert-syn.man \
+	lib/ssl-peer-ca-cert.man \
+	lib/ssl-syn.man \
+	lib/ssl.man \
+	lib/unixctl-syn.man \
+	lib/unixctl.man \
+	lib/vlog-syn.man \
+	lib/vlog-unixctl.man \
+	lib/vlog.man
+ovsdb/ovsdb-server.1.in:
+lib/common-syn.man:
+lib/common.man:
+lib/coverage-unixctl.man:
+lib/daemon-syn.man:
+lib/daemon.man:
+lib/memory-unixctl.man:
+lib/service-syn.man:
+lib/service.man:
+lib/ssl-bootstrap-syn.man:
+lib/ssl-bootstrap.man:
+lib/ssl-connect-syn.man:
+lib/ssl-connect.man:
+lib/ssl-peer-ca-cert-syn.man:
+lib/ssl-peer-ca-cert.man:
+lib/ssl-syn.man:
+lib/ssl.man:
+lib/unixctl-syn.man:
+lib/unixctl.man:
+lib/vlog-syn.man:
+lib/vlog-unixctl.man:
+lib/vlog.man:
+
+ovsdb/ovsdb-tool.1: \
+	ovsdb/ovsdb-tool.1.in \
+	lib/common-syn.man \
+	lib/common.man \
+	lib/vlog-syn.man \
+	lib/vlog.man \
+	ovsdb/ovsdb-schemas.man
+ovsdb/ovsdb-tool.1.in:
+lib/common-syn.man:
+lib/common.man:
+lib/vlog-syn.man:
+lib/vlog.man:
+ovsdb/ovsdb-schemas.man:
+
+utilities/bugtool/ovs-bugtool.8: \
+	utilities/bugtool/ovs-bugtool.8.in
+utilities/bugtool/ovs-bugtool.8.in:
+
+utilities/ovs-appctl.8: \
+	utilities/ovs-appctl.8.in \
+	lib/common.man
+utilities/ovs-appctl.8.in:
+lib/common.man:
+
+utilities/ovs-dpctl-top.8: \
+	utilities/ovs-dpctl-top.8.in
+utilities/ovs-dpctl-top.8.in:
+
+utilities/ovs-dpctl.8: \
+	utilities/ovs-dpctl.8.in \
+	lib/common.man \
+	lib/dpctl.man \
+	lib/vlog.man
+utilities/ovs-dpctl.8.in:
+lib/common.man:
+lib/dpctl.man:
+lib/vlog.man:
+
+utilities/ovs-l3ping.8: \
+	utilities/ovs-l3ping.8.in \
+	lib/common-syn.man \
+	lib/common.man
+utilities/ovs-l3ping.8.in:
+lib/common-syn.man:
+lib/common.man:
+
+utilities/ovs-ofctl.8: \
+	utilities/ovs-ofctl.8.in \
+	lib/colors.man \
+	lib/common.man \
+	lib/daemon.man \
+	lib/ofp-version.man \
+	lib/ssl.man \
+	lib/unixctl.man \
+	lib/vconn-active.man \
+	lib/vlog.man
+utilities/ovs-ofctl.8.in:
+lib/colors.man:
+lib/common.man:
+lib/daemon.man:
+lib/ofp-version.man:
+lib/ssl.man:
+lib/unixctl.man:
+lib/vconn-active.man:
+lib/vlog.man:
+
+utilities/ovs-pcap.1: \
+	utilities/ovs-pcap.1.in \
+	lib/common-syn.man \
+	lib/common.man
+utilities/ovs-pcap.1.in:
+lib/common-syn.man:
+lib/common.man:
+
+utilities/ovs-pki.8: \
+	utilities/ovs-pki.8.in
+utilities/ovs-pki.8.in:
+
+utilities/ovs-tcpdump.8: \
+	utilities/ovs-tcpdump.8.in \
+	lib/common.man
+utilities/ovs-tcpdump.8.in:
+lib/common.man:
+
+utilities/ovs-tcpundump.1: \
+	utilities/ovs-tcpundump.1.in \
+	lib/common-syn.man \
+	lib/common.man
+utilities/ovs-tcpundump.1.in:
+lib/common-syn.man:
+lib/common.man:
+
+utilities/ovs-testcontroller.8: \
+	utilities/ovs-testcontroller.8.in \
+	lib/common.man \
+	lib/daemon.man \
+	lib/ofp-version.man \
+	lib/ssl-peer-ca-cert.man \
+	lib/ssl.man \
+	lib/unixctl.man \
+	lib/vconn-active.man \
+	lib/vconn-passive.man \
+	lib/vlog.man
+utilities/ovs-testcontroller.8.in:
+lib/common.man:
+lib/daemon.man:
+lib/ofp-version.man:
+lib/ssl-peer-ca-cert.man:
+lib/ssl.man:
+lib/unixctl.man:
+lib/vconn-active.man:
+lib/vconn-passive.man:
+lib/vlog.man:
+
+utilities/ovs-vlan-bug-workaround.8: \
+	utilities/ovs-vlan-bug-workaround.8.in \
+	lib/common.man \
+	utilities/ovs-vlan-bugs.man
+utilities/ovs-vlan-bug-workaround.8.in:
+lib/common.man:
+utilities/ovs-vlan-bugs.man:
+
+utilities/ovs-vsctl.8: \
+	utilities/ovs-vsctl.8.in \
+	lib/common.man \
+	lib/db-ctl-base.man \
+	lib/ssl-bootstrap.man \
+	lib/ssl-peer-ca-cert.man \
+	lib/ssl.man \
+	lib/table.man \
+	lib/vconn-active.man \
+	lib/vconn-passive.man \
+	lib/vlog.man
+utilities/ovs-vsctl.8.in:
+lib/common.man:
+lib/db-ctl-base.man:
+lib/ssl-bootstrap.man:
+lib/ssl-peer-ca-cert.man:
+lib/ssl.man:
+lib/table.man:
+lib/vconn-active.man:
+lib/vconn-passive.man:
+lib/vlog.man:
+
+vswitchd/ovs-vswitchd.8: \
+	vswitchd/ovs-vswitchd.8.in \
+	lib/common.man \
+	lib/coverage-unixctl.man \
+	lib/daemon.man \
+	lib/dpctl.man \
+	lib/memory-unixctl.man \
+	lib/service.man \
+	lib/ssl-bootstrap.man \
+	lib/ssl.man \
+	lib/unixctl.man \
+	lib/vlog-unixctl.man \
+	lib/vlog.man \
+	ofproto/ofproto-dpif-unixctl.man \
+	ofproto/ofproto-tnl-unixctl.man \
+	ofproto/ofproto-unixctl.man
+vswitchd/ovs-vswitchd.8.in:
+lib/common.man:
+lib/coverage-unixctl.man:
+lib/daemon.man:
+lib/dpctl.man:
+lib/memory-unixctl.man:
+lib/service.man:
+lib/ssl-bootstrap.man:
+lib/ssl.man:
+lib/unixctl.man:
+lib/vlog-unixctl.man:
+lib/vlog.man:
+ofproto/ofproto-dpif-unixctl.man:
+ofproto/ofproto-tnl-unixctl.man:
+ofproto/ofproto-unixctl.man:
+
+vtep/vtep-ctl.8: \
+	vtep/vtep-ctl.8.in \
+	lib/common.man \
+	lib/db-ctl-base.man \
+	lib/ssl-bootstrap.man \
+	lib/ssl-peer-ca-cert.man \
+	lib/ssl.man \
+	lib/table.man \
+	lib/vlog.man
+vtep/vtep-ctl.8.in:
+lib/common.man:
+lib/db-ctl-base.man:
+lib/ssl-bootstrap.man:
+lib/ssl-peer-ca-cert.man:
+lib/ssl.man:
+lib/table.man:
+lib/vlog.man:
diff --git a/ovsdb/log.c b/ovsdb/log.c
index adc14761cd8a..7f05fb083246 100644
--- a/ovsdb/log.c
+++ b/ovsdb/log.c
@@ -116,7 +116,13 @@ ovsdb_log_open(const char *name, const char *magic,
 #ifdef _WIN32
     flags = flags | O_BINARY;
 #endif
-    fd = open(name, flags, 0666);
+    /* Special case for /dev/stdin to make it work even if the operating system
+     * doesn't support it under that name. */
+    if (!strcmp(name, "/dev/stdin") && open_mode == OVSDB_LOG_READ_ONLY) {
+        fd = dup(STDIN_FILENO);
+    } else {
+        fd = open(name, flags, 0666);
+    }
     if (fd < 0) {
         const char *op = (open_mode == OVSDB_LOG_CREATE_EXCL ? "create"
             : open_mode == OVSDB_LOG_CREATE ? "create or open"
diff --git a/ovsdb/ovsdb-client.1.in b/ovsdb/ovsdb-client.1.in
index 26f007258c09..cd17467147da 100644
--- a/ovsdb/ovsdb-client.1.in
+++ b/ovsdb/ovsdb-client.1.in
@@ -36,6 +36,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
@@ -44,7 +47,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
@@ -203,6 +205,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 bfffc08effb0..056aa25c8983 100644
--- a/ovsdb/ovsdb-client.c
+++ b/ovsdb/ovsdb-client.c
@@ -41,6 +41,7 @@
 #include "ovsdb-data.h"
 #include "ovsdb-error.h"
 #include "poll-loop.h"
+#include "row.h"
 #include "sort.h"
 #include "svec.h"
 #include "stream.h"
@@ -85,6 +86,9 @@ static bool timestamp;
  * actually works.) */
 static int db_change_aware = -1;
 
+/* --force: Ignore schema differences for "restore" command? */
+static bool force;
+
 /* Format for table output. */
 static struct table_style table_style = TABLE_STYLE_DEFAULT;
 
@@ -196,6 +200,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,
@@ -207,6 +212,7 @@ parse_options(int argc, char *argv[])
         {"timestamp", no_argument, NULL, OPT_TIMESTAMP},
         {"db-change-aware", no_argument, &db_change_aware, 1},
         {"no-db-change-aware", no_argument, &db_change_aware, 0},
+        {"force", no_argument, NULL, OPT_FORCE},
         VLOG_LONG_OPTIONS,
         DAEMON_LONG_OPTIONS,
 #ifdef HAVE_OPENSSL
@@ -249,6 +255,10 @@ parse_options(int argc, char *argv[])
             timestamp = true;
             break;
 
+        case OPT_FORCE:
+            force = true;
+            break;
+
         case '?':
             exit(EXIT_FAILURE);
 
@@ -304,6 +314,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"
@@ -1607,6 +1619,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)
 {
@@ -1821,6 +1928,7 @@ static const struct ovsdb_client_command all_commands[] = {
     { "needs-conversion",   NEED_RPC,      1, 1,       do_needs_conversion },
     { "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/ovsdb/ovsdb.7.xml b/ovsdb/ovsdb.7.xml
index 5461f252a03a..8169120c88f2 100644
--- a/ovsdb/ovsdb.7.xml
+++ b/ovsdb/ovsdb.7.xml
@@ -410,9 +410,16 @@
   </p>
 
   <p>
-    To restore from a backup, stop the database server or servers, overwrite
-    the database file with the backup (e.g. with <code>cp</code>), 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
+    <code>cp</code>), and then restart the servers.  Another way is to use
+    <code>ovsdb-client restore</code>, which connects to a running database
+    server and replaces the data in one of its databases by a provided
+    snapshot.  Using <code>ovsdb-client restore</code> 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.
   </p>
 
   <p>
-- 
2.10.2



More information about the dev mailing list