summaryrefslogtreecommitdiff
path: root/ovsdb
diff options
context:
space:
mode:
authorBen Pfaff <blp@ovn.org>2017-12-21 16:41:30 -0800
committerBen Pfaff <blp@ovn.org>2017-12-21 16:41:30 -0800
commitfe0fb88551b4cc5b4bee6814f1027f78c451daa2 (patch)
tree5cac6efc848ef3dba694a9219e2da8632bdcf23f /ovsdb
parent62705b81108ff6d011362331847cf0ba22494779 (diff)
downloadopenvswitch-fe0fb88551b4cc5b4bee6814f1027f78c451daa2.tar.gz
ovsdb-client: Add new "restore" command.
Signed-off-by: Ben Pfaff <blp@ovn.org>
Diffstat (limited to 'ovsdb')
-rw-r--r--ovsdb/ovsdb-client.1.in33
-rw-r--r--ovsdb/ovsdb-client.c110
2 files changed, 139 insertions, 4 deletions
diff --git a/ovsdb/ovsdb-client.1.in b/ovsdb/ovsdb-client.1.in
index 61cd792af..7ab452f6d 100644
--- a/ovsdb/ovsdb-client.1.in
+++ b/ovsdb/ovsdb-client.1.in
@@ -31,7 +31,10 @@ ovsdb\-client \- command-line interface to \fBovsdb-server\fR(1)
[\fIcolumn\fR...]]
.br
\fBovsdb\-client \fR[\fIoptions\fR]
-\fBbackup \fR[\fIserver\fR] [\fIdatabase\fR] > \fIsnapshot\fR
+\fBbackup \fR[\fIserver\fR] [\fIdatabase\fR] \fB> \fIsnapshot\fR
+.br
+\fBovsdb\-client \fR[\fIoptions\fR] [\fB\-\-force\fR]
+\fBrestore \fR[\fIserver\fR] [\fIdatabase\fR] \fB< \fIsnapshot\fR
.br
\fBovsdb\-client \fR[\fIoptions\fR] \fBmonitor\fI \fR[\fIserver\fR] \fR[\fIdatabase\fR] \fItable\fR
[\fIcolumn\fR[\fB,\fIcolumn\fR]...]...
@@ -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
@@ -141,7 +143,7 @@ and prints it on stdout as a series of tables. If \fItable\fR is
specified, only that table is retrieved. If at least one \fIcolumn\fR
is specified, only those columns are retrieved.
.
-.IP "\fBbackup \fR[\fIserver\fR] [\fIdatabase\fR] > \fIsnapshot\fR"
+.IP "\fBbackup \fR[\fIserver\fR] [\fIdatabase\fR] \fB> \fIsnapshot\fR"
Connects to \fIserver\fR, retrieves a snapshot of the schema and data
in \fIdatabase\fR, and prints it on stdout in the format used for
OVSDB database files. This is an appropriate
@@ -156,6 +158,31 @@ 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 \fR[\fIserver\fR] [\fIdatabase\fR] \fB< \fIsnapshot\fR"
+Reads \fIsnapshot\fR, which must be a OVSDB standalone or
+active-backup database (possibly but not necessarily created by
+\fBovsdb\-client backup). 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 568c46b84..349bc55d4 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);
@@ -277,8 +287,10 @@ usage(void)
" in DATBASE on SERVER.\n"
"\n dump [SERVER] [DATABASE]\n"
" dump contents of DATABASE on SERVER to stdout\n"
- "\n backup [SERVER] [DATABASE] > DB\n"
+ "\n backup [SERVER] [DATABASE] > SNAPSHOT\n"
" dump database contents in the form of a database file\n"
+ "\n [--force] restore [SERVER] [DATABASE] < SNAPSHOT\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 },