Signed-off-by: Ben Pfaff <[email protected]>
---
Documentation/ref/ovsdb.7.rst | 4 ++
NEWS | 1 +
ovsdb/file.c | 22 ++++---
ovsdb/file.h | 2 +
ovsdb/ovsdb-client.1.in | 18 ++++++
ovsdb/ovsdb-client.c | 134 ++++++++++++++++++++++++++++++++++++++++++
tests/ovsdb-client.at | 51 ++++++++++++++++
7 files changed, 224 insertions(+), 8 deletions(-)
diff --git a/Documentation/ref/ovsdb.7.rst b/Documentation/ref/ovsdb.7.rst
index 1392ce6f72d7..ddc573a47c94 100644
--- a/Documentation/ref/ovsdb.7.rst
+++ b/Documentation/ref/ovsdb.7.rst
@@ -313,6 +313,10 @@ database file, e.g. using ``cp``, effectively makes a
snapshot, and because
OVSDB database files are append-only, it works even if the database is being
modified when the snapshot takes place.
+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.
diff --git a/NEWS b/NEWS
index 49d2fa597b97..752e98f073e1 100644
--- a/NEWS
+++ b/NEWS
@@ -6,6 +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.
- OVN:
* The "requested-chassis" option for a logical switch port now accepts a
chassis "hostname" in addition to a chassis "name".
diff --git a/ovsdb/file.c b/ovsdb/file.c
index 2f07bba3d30c..2986ce08062d 100644
--- a/ovsdb/file.c
+++ b/ovsdb/file.c
@@ -571,6 +571,19 @@ ovsdb_file_change_cb(const struct ovsdb_row *old,
return true;
}
+struct json *
+ovsdb_file_txn_annotate(struct json *json, const char *comment)
+{
+ if (!json) {
+ json = json_object_create();
+ }
+ if (comment) {
+ json_object_put_string(json, "_comment", comment);
+ }
+ json_object_put(json, "_date", json_integer_create(time_wall_msec()));
+ return json;
+}
+
static struct ovsdb_error *
ovsdb_file_commit(struct ovsdb_replica *replica,
const struct ovsdb_txn *txn, bool durable)
@@ -843,14 +856,7 @@ ovsdb_file_txn_commit(struct json *json, const char
*comment,
{
struct ovsdb_error *error;
- if (!json) {
- json = json_object_create();
- }
- if (comment) {
- json_object_put_string(json, "_comment", comment);
- }
- json_object_put(json, "_date", json_integer_create(time_wall_msec()));
-
+ json = ovsdb_file_txn_annotate(json, comment);
error = ovsdb_log_write(log, json);
json_destroy(json);
if (error) {
diff --git a/ovsdb/file.h b/ovsdb/file.h
index ee67b127dd25..c6ca92621044 100644
--- a/ovsdb/file.h
+++ b/ovsdb/file.h
@@ -44,4 +44,6 @@ struct ovsdb_error *ovsdb_file_read_schema(const char
*file_name,
struct ovsdb_schema **)
OVS_WARN_UNUSED_RESULT;
+struct json *ovsdb_file_txn_annotate(struct json *, const char *comment);
+
#endif /* ovsdb/file.h */
diff --git a/ovsdb/ovsdb-client.1.in b/ovsdb/ovsdb-client.1.in
index 694a7abed46d..2e2df5e5aa7f 100644
--- a/ovsdb/ovsdb-client.1.in
+++ b/ovsdb/ovsdb-client.1.in
@@ -30,6 +30,9 @@ ovsdb\-client \- command-line interface to
\fBovsdb-server\fR(1)
\fBovsdb\-client \fR[\fIoptions\fR] \fBdump\fI \fR[\fIserver\fR]
\fR[\fIdatabase\fR]\fR [\fItable\fR
[\fIcolumn\fR...]]
.br
+\fBovsdb\-client \fR[\fIoptions\fR]
+\fBbackup\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
@@ -138,6 +141,21 @@ 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\fI \fR[\fIserver\fR] \fR[\fIdatabase\fR] > \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 standalone and active-backup database. This is an appropriate
+way to back up a remote database. The database snapshot that it
+outputs is suitable to be served up directly by \fBovsdb\-server\fR or
+used as the input to \fBovsdb\-client restore\fR.
+.IP
+Another way to back up a standalone or active-backup database is to
+copy its database file, e.g. with \fBcp\fR. This is safe even if the
+database is in use.
+.IP
+The output does not include ephemeral columns, which by design do not
+survive across restarts of \fBovsdb\-server\fR.
+.
.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 c37302ef96f6..568c46b84d54 100644
--- a/ovsdb/ovsdb-client.c
+++ b/ovsdb/ovsdb-client.c
@@ -32,9 +32,11 @@
#include "dirs.h"
#include "openvswitch/dynamic-string.h"
#include "fatal-signal.h"
+#include "file.h"
#include "openvswitch/json.h"
#include "jsonrpc.h"
#include "lib/table.h"
+#include "log.h"
#include "ovsdb.h"
#include "ovsdb-data.h"
#include "ovsdb-error.h"
@@ -275,6 +277,8 @@ 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"
+ " dump database contents in the form of a database file\n"
"\n lock [SERVER] LOCK\n"
" create or wait for LOCK in SERVER\n"
"\n steal [SERVER] LOCK\n"
@@ -1383,6 +1387,135 @@ do_dump(struct jsonrpc *rpc, const char *database,
}
static void
+print_and_free_log_record(struct json *record)
+{
+ struct ds header = DS_EMPTY_INITIALIZER;
+ struct ds data = DS_EMPTY_INITIALIZER;
+ ovsdb_log_compose_record(record, &header, &data);
+ fwrite(header.string, header.length, 1, stdout);
+ fwrite(data.string, data.length, 1, stdout);
+ ds_destroy(&data);
+ ds_destroy(&header);
+ json_destroy(record);
+}
+
+static void
+do_backup(struct jsonrpc *rpc, const char *database,
+ int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+{
+ if (isatty(STDOUT_FILENO)) {
+ ovs_fatal(0, "not writing backup to a terminal; "
+ "please redirect stdout to a file");
+ }
+
+ /* Get schema. */
+ struct ovsdb_schema *schema = fetch_schema(rpc, database);
+
+ /* Construct transaction to retrieve all tables. */
+ struct json *txn = json_array_create_1(json_string_create(database));
+ struct shash_node *node;
+ SHASH_FOR_EACH (node, &schema->tables) {
+ const char *table_name = node->name;
+ const struct ovsdb_table_schema *table = node->data;
+
+ /* Get all the columns except _version and the ephemeral ones.
+ *
+ * We don't omit tables that only have ephemeral columns because of the
+ * possibility that other tables references rows in those tables; that
+ * is, even if all the columns are ephemeral, the rows themselves are
+ * not. */
+ struct json *columns = json_array_create_empty();
+ struct shash_node *node2;
+ SHASH_FOR_EACH (node2, &table->columns) {
+ const struct ovsdb_column *column = node2->data;
+
+ if (column->persistent) {
+ if (!columns) {
+ columns = json_array_create_empty();
+ }
+ json_array_add(columns, json_string_create(column->name));
+ }
+ }
+
+ struct json *op = json_object_create();
+ json_object_put_string(op, "op", "select");
+ json_object_put_string(op, "table", table_name);
+ json_object_put(op, "where", json_array_create_empty());
+ json_object_put(op, "columns", columns);
+ json_array_add(txn, op);
+ }
+
+ /* Send request, get reply. */
+ struct jsonrpc_msg *rq = jsonrpc_create_request("transact", txn, NULL);
+ struct jsonrpc_msg *reply;
+ check_txn(jsonrpc_transact_block(rpc, rq, &reply), &reply);
+
+ /* Print schema record. */
+ print_and_free_log_record(ovsdb_schema_to_json(schema));
+
+ /* Print database transaction record. */
+ if (reply->result->type != JSON_ARRAY
+ || reply->result->u.array.n != shash_count(&schema->tables)) {
+ ovs_fatal(0, "reply is not array of %"PRIuSIZE" elements: %s",
+ shash_count(&schema->tables),
+ json_to_string(reply->result, 0));
+ }
+ struct json *output_txn = json_object_create();
+
+ size_t i = 0;
+ SHASH_FOR_EACH (node, &schema->tables) {
+ const char *table_name = node->name;
+ const struct ovsdb_table_schema *table = node->data;
+ const struct json *op_result = reply->result->u.array.elems[i++];
+ struct json *rows;
+
+ if (op_result->type != JSON_OBJECT
+ || !(rows = shash_find_data(json_object(op_result), "rows"))
+ || rows->type != JSON_ARRAY) {
+ ovs_fatal(0, "%s table reply is not an object with a \"rows\" "
+ "member array: %s",
+ table->name, json_to_string(op_result, 0));
+ }
+
+ if (!rows->u.array.n) {
+ continue;
+ }
+
+ struct json *output_rows = json_object_create();
+ for (size_t j = 0; j < rows->u.array.n; j++) {
+ struct json *row = rows->u.array.elems[j];
+ if (row->type != JSON_OBJECT) {
+ ovs_fatal(0, "%s table reply row is not an object: %s",
+ table_name, json_to_string(row, 0));
+ }
+
+ struct json *uuid_json = shash_find_and_delete(json_object(row),
+ "_uuid");
+ if (!uuid_json) {
+ ovs_fatal(0, "%s table reply row lacks _uuid member: %s",
+ table_name, json_to_string(row, 0));
+ }
+
+ const struct ovsdb_base_type uuid_base = OVSDB_BASE_UUID_INIT;
+ union ovsdb_atom atom;
+ check_ovsdb_error(ovsdb_atom_from_json(&atom, &uuid_base,
+ uuid_json, NULL));
+
+ char uuid_s[UUID_LEN + 1];
+ snprintf(uuid_s, sizeof uuid_s, UUID_FMT, UUID_ARGS(&atom.uuid));
+ json_object_put(output_rows, uuid_s, json_clone(row));
+ }
+ json_object_put(output_txn, table_name, output_rows);
+ }
+ output_txn = ovsdb_file_txn_annotate(
+ output_txn, "produced by \"ovsdb-client backup\"");
+ print_and_free_log_record(output_txn);
+
+ ovsdb_schema_destroy(schema);
+ 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)
{
@@ -1594,6 +1727,7 @@ static const struct ovsdb_client_command all_commands[] =
{
{ "monitor", NEED_DATABASE, 1, INT_MAX, do_monitor },
{ "monitor-cond", NEED_DATABASE, 2, 3, do_monitor_cond },
{ "dump", NEED_DATABASE, 0, INT_MAX, do_dump },
+ { "backup", NEED_DATABASE, 0, 0, do_backup },
{ "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 903929550ec2..b35e9f75e972 100644
--- a/tests/ovsdb-client.at
+++ b/tests/ovsdb-client.at
@@ -11,3 +11,54 @@ AT_CHECK([ovsdb-client get-schema-cksum unix:socket
ordinals], [0], [12345678 9
])
OVSDB_SERVER_SHUTDOWN
AT_CLEANUP
+
+AT_SETUP([ovsdb-client backup])
+AT_KEYWORDS([ovsdb client positive])
+
+on_exit 'kill `cat *.pid`'
+
+dnl Create a database.
+ordinal_schema > schema
+touch .db.~lock~
+AT_CHECK([ovsdb-tool create db schema])
+
+dnl Put some data in the database.
+AT_CHECK(
+ [[for pair in 'zero 0' 'one 1' 'two 2' 'three 3' 'four 4' 'five 5'; do
+ set -- $pair
+ ovsdb-tool transact db '
+ ["ordinals",
+ {"op": "insert",
+ "table": "ordinals",
+ "row": {"name": "'$1'", "number": '$2'}},
+ {"op": "comment",
+ "comment": "add row for '"$pair"'"}]'
+ done | uuidfilt]], [0],
+[[[{"uuid":["uuid","<0>"]},{}]
+[{"uuid":["uuid","<1>"]},{}]
+[{"uuid":["uuid","<2>"]},{}]
+[{"uuid":["uuid","<3>"]},{}]
+[{"uuid":["uuid","<4>"]},{}]
+[{"uuid":["uuid","<5>"]},{}]
+]], [ignore])
+
+dnl Start the database server.
+AT_CHECK([ovsdb-server -vfile -vvlog:off --detach --no-chdir --pidfile
--log-file --remote=punix:db.sock db], [0])
+AT_CAPTURE_FILE([ovsdb-server.log])
+
+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 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])
+
+dnl Dump a new copy of the data.
+AT_CHECK([ovsdb-client dump > dump2])
+sort dump2 > expout
+
+dnl Verify that the two dumps are the same.
+AT_CHECK([sort dump1], [0], [expout])
+
+AT_CLEANUP
--
2.10.2
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev