Patch Detail
get:
Show a patch.
patch:
Update a patch.
put:
Update a patch.
GET /api/patches/815901/?format=api
{ "id": 815901, "url": "http://patchwork.ozlabs.org/api/patches/815901/?format=api", "web_url": "http://patchwork.ozlabs.org/project/openvswitch/patch/20170919220125.32535-49-blp@ovn.org/", "project": { "id": 47, "url": "http://patchwork.ozlabs.org/api/projects/47/?format=api", "name": "Open vSwitch", "link_name": "openvswitch", "list_id": "ovs-dev.openvswitch.org", "list_email": "ovs-dev@openvswitch.org", "web_url": "http://openvswitch.org/", "scm_url": "git@github.com:openvswitch/ovs.git", "webscm_url": "https://github.com/openvswitch/ovs", "list_archive_url": "", "list_archive_url_format": "", "commit_url_format": "" }, "msgid": "<20170919220125.32535-49-blp@ovn.org>", "list_archive_url": null, "date": "2017-09-19T22:01:21", "name": "[ovs-dev,RFC,48/52] ovsdb-client: Add new \"backup\" command.", "commit_ref": null, "pull_url": null, "state": "rfc", "archived": false, "hash": "fdab565fc5aa83cd70f6db87fbd5d3683aa93e15", "submitter": { "id": 67603, "url": "http://patchwork.ozlabs.org/api/people/67603/?format=api", "name": "Ben Pfaff", "email": "blp@ovn.org" }, "delegate": null, "mbox": "http://patchwork.ozlabs.org/project/openvswitch/patch/20170919220125.32535-49-blp@ovn.org/mbox/", "series": [ { "id": 3975, "url": "http://patchwork.ozlabs.org/api/series/3975/?format=api", "web_url": "http://patchwork.ozlabs.org/project/openvswitch/list/?series=3975", "date": "2017-09-19T22:00:34", "name": "clustering implementation", "version": 1, "mbox": "http://patchwork.ozlabs.org/series/3975/mbox/" } ], "comments": "http://patchwork.ozlabs.org/api/patches/815901/comments/", "check": "pending", "checks": "http://patchwork.ozlabs.org/api/patches/815901/checks/", "tags": {}, "related": [], "headers": { "Return-Path": "<ovs-dev-bounces@openvswitch.org>", "X-Original-To": [ "incoming@patchwork.ozlabs.org", "dev@openvswitch.org" ], "Delivered-To": [ "patchwork-incoming@bilbo.ozlabs.org", "ovs-dev@mail.linuxfoundation.org" ], "Authentication-Results": "ozlabs.org;\n\tspf=pass (mailfrom) smtp.mailfrom=openvswitch.org\n\t(client-ip=140.211.169.12; helo=mail.linuxfoundation.org;\n\tenvelope-from=ovs-dev-bounces@openvswitch.org;\n\treceiver=<UNKNOWN>)", "Received": [ "from mail.linuxfoundation.org (mail.linuxfoundation.org\n\t[140.211.169.12])\n\t(using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256\n\tbits)) (No client certificate requested)\n\tby ozlabs.org (Postfix) with ESMTPS id 3xxcsl24RSz9sBW\n\tfor <incoming@patchwork.ozlabs.org>;\n\tWed, 20 Sep 2017 08:26:19 +1000 (AEST)", "from mail.linux-foundation.org (localhost [127.0.0.1])\n\tby mail.linuxfoundation.org (Postfix) with ESMTP id A46BDD53;\n\tTue, 19 Sep 2017 22:02:58 +0000 (UTC)", "from smtp1.linuxfoundation.org (smtp1.linux-foundation.org\n\t[172.17.192.35])\n\tby mail.linuxfoundation.org (Postfix) with ESMTPS id 36304D49\n\tfor <dev@openvswitch.org>; Tue, 19 Sep 2017 22:02:57 +0000 (UTC)", "from relay4-d.mail.gandi.net (relay4-d.mail.gandi.net\n\t[217.70.183.196])\n\tby smtp1.linuxfoundation.org (Postfix) with ESMTPS id 97F8E3D4\n\tfor <dev@openvswitch.org>; Tue, 19 Sep 2017 22:02:55 +0000 (UTC)", "from sigabrt.benpfaff.org (unknown [208.91.2.3])\n\t(Authenticated sender: blp@ovn.org)\n\tby relay4-d.mail.gandi.net (Postfix) with ESMTPSA id 0DC18172094;\n\tWed, 20 Sep 2017 00:02:52 +0200 (CEST)" ], "X-Greylist": "domain auto-whitelisted by SQLgrey-1.7.6", "X-Originating-IP": "208.91.2.3", "From": "Ben Pfaff <blp@ovn.org>", "To": "dev@openvswitch.org", "Date": "Tue, 19 Sep 2017 15:01:21 -0700", "Message-Id": "<20170919220125.32535-49-blp@ovn.org>", "X-Mailer": "git-send-email 2.10.2", "In-Reply-To": "<20170919220125.32535-1-blp@ovn.org>", "References": "<20170919220125.32535-1-blp@ovn.org>", "X-Spam-Status": "No, score=-0.7 required=5.0 tests=RCVD_IN_DNSWL_LOW\n\tautolearn=disabled version=3.3.1", "X-Spam-Checker-Version": "SpamAssassin 3.3.1 (2010-03-16) on\n\tsmtp1.linux-foundation.org", "Cc": "Ben Pfaff <blp@ovn.org>", "Subject": "[ovs-dev] [PATCH RFC 48/52] ovsdb-client: Add new \"backup\" command.", "X-BeenThere": "ovs-dev@openvswitch.org", "X-Mailman-Version": "2.1.12", "Precedence": "list", "List-Id": "<ovs-dev.openvswitch.org>", "List-Unsubscribe": "<https://mail.openvswitch.org/mailman/options/ovs-dev>,\n\t<mailto:ovs-dev-request@openvswitch.org?subject=unsubscribe>", "List-Archive": "<http://mail.openvswitch.org/pipermail/ovs-dev/>", "List-Post": "<mailto:ovs-dev@openvswitch.org>", "List-Help": "<mailto:ovs-dev-request@openvswitch.org?subject=help>", "List-Subscribe": "<https://mail.openvswitch.org/mailman/listinfo/ovs-dev>,\n\t<mailto:ovs-dev-request@openvswitch.org?subject=subscribe>", "MIME-Version": "1.0", "Content-Type": "text/plain; charset=\"us-ascii\"", "Content-Transfer-Encoding": "7bit", "Sender": "ovs-dev-bounces@openvswitch.org", "Errors-To": "ovs-dev-bounces@openvswitch.org" }, "content": "Signed-off-by: Ben Pfaff <blp@ovn.org>\n---\n NEWS | 1 +\n manpages.mk | 299 ------------------------------------------------\n ovsdb/file.c | 22 ++--\n ovsdb/file.h | 2 +\n ovsdb/log.c | 58 ++++++----\n ovsdb/log.h | 4 +\n ovsdb/ovsdb-client.1.in | 18 +++\n ovsdb/ovsdb-client.c | 134 ++++++++++++++++++++++\n ovsdb/ovsdb.7.xml | 6 +\n tests/ovsdb-client.at | 55 ++++++++-\n 10 files changed, 265 insertions(+), 334 deletions(-)", "diff": "diff --git a/NEWS b/NEWS\nindex 066f88215627..686c387d782a 100644\n--- a/NEWS\n+++ b/NEWS\n@@ -8,6 +8,7 @@ Post-v2.8.0\n * ovsdb-server now always hosts a built-in database named _Server. See\n ovsdb-server(5) for more details.\n * ovsdb-client: New \"get-schema-cksum\" command.\n+ * ovsdb-client: New \"backup\" command.\n - ovs-vsctl and other commands that display data in tables now support a\n --max-column-width option to limit column width.\n - OVN:\ndiff --git a/manpages.mk b/manpages.mk\nindex 7d6a507e0039..e69de29bb2d1 100644\n--- a/manpages.mk\n+++ b/manpages.mk\n@@ -1,299 +0,0 @@\n-# Generated automatically -- do not modify! -*- buffer-read-only: t -*-\n-\n-ovn/utilities/ovn-detrace.1: \\\n-\tovn/utilities/ovn-detrace.1.in \\\n-\tlib/common-syn.man \\\n-\tlib/common.man\n-ovn/utilities/ovn-detrace.1.in:\n-lib/common-syn.man:\n-lib/common.man:\n-\n-ovn/utilities/ovn-sbctl.8: \\\n-\tovn/utilities/ovn-sbctl.8.in \\\n-\tlib/common.man \\\n-\tlib/db-ctl-base.man \\\n-\tlib/ssl-bootstrap.man \\\n-\tlib/ssl-peer-ca-cert.man \\\n-\tlib/ssl.man \\\n-\tlib/table.man \\\n-\tlib/vlog.man\n-ovn/utilities/ovn-sbctl.8.in:\n-lib/common.man:\n-lib/db-ctl-base.man:\n-lib/ssl-bootstrap.man:\n-lib/ssl-peer-ca-cert.man:\n-lib/ssl.man:\n-lib/table.man:\n-lib/vlog.man:\n-\n-ovsdb/ovsdb-client.1: \\\n-\tovsdb/ovsdb-client.1.in \\\n-\tlib/common-syn.man \\\n-\tlib/common.man \\\n-\tlib/daemon-syn.man \\\n-\tlib/daemon.man \\\n-\tlib/ssl-bootstrap-syn.man \\\n-\tlib/ssl-bootstrap.man \\\n-\tlib/ssl-connect-syn.man \\\n-\tlib/ssl-connect.man \\\n-\tlib/ssl-syn.man \\\n-\tlib/ssl.man \\\n-\tlib/table.man \\\n-\tlib/vlog-syn.man \\\n-\tlib/vlog.man \\\n-\tovsdb/ovsdb-schemas.man\n-ovsdb/ovsdb-client.1.in:\n-lib/common-syn.man:\n-lib/common.man:\n-lib/daemon-syn.man:\n-lib/daemon.man:\n-lib/ssl-bootstrap-syn.man:\n-lib/ssl-bootstrap.man:\n-lib/ssl-connect-syn.man:\n-lib/ssl-connect.man:\n-lib/ssl-syn.man:\n-lib/ssl.man:\n-lib/table.man:\n-lib/vlog-syn.man:\n-lib/vlog.man:\n-ovsdb/ovsdb-schemas.man:\n-\n-ovsdb/ovsdb-server.1: \\\n-\tovsdb/ovsdb-server.1.in \\\n-\tlib/common-syn.man \\\n-\tlib/common.man \\\n-\tlib/coverage-unixctl.man \\\n-\tlib/daemon-syn.man \\\n-\tlib/daemon.man \\\n-\tlib/memory-unixctl.man \\\n-\tlib/service-syn.man \\\n-\tlib/service.man \\\n-\tlib/ssl-bootstrap-syn.man \\\n-\tlib/ssl-bootstrap.man \\\n-\tlib/ssl-connect-syn.man \\\n-\tlib/ssl-connect.man \\\n-\tlib/ssl-peer-ca-cert-syn.man \\\n-\tlib/ssl-peer-ca-cert.man \\\n-\tlib/ssl-syn.man \\\n-\tlib/ssl.man \\\n-\tlib/unixctl-syn.man \\\n-\tlib/unixctl.man \\\n-\tlib/vlog-syn.man \\\n-\tlib/vlog-unixctl.man \\\n-\tlib/vlog.man\n-ovsdb/ovsdb-server.1.in:\n-lib/common-syn.man:\n-lib/common.man:\n-lib/coverage-unixctl.man:\n-lib/daemon-syn.man:\n-lib/daemon.man:\n-lib/memory-unixctl.man:\n-lib/service-syn.man:\n-lib/service.man:\n-lib/ssl-bootstrap-syn.man:\n-lib/ssl-bootstrap.man:\n-lib/ssl-connect-syn.man:\n-lib/ssl-connect.man:\n-lib/ssl-peer-ca-cert-syn.man:\n-lib/ssl-peer-ca-cert.man:\n-lib/ssl-syn.man:\n-lib/ssl.man:\n-lib/unixctl-syn.man:\n-lib/unixctl.man:\n-lib/vlog-syn.man:\n-lib/vlog-unixctl.man:\n-lib/vlog.man:\n-\n-ovsdb/ovsdb-tool.1: \\\n-\tovsdb/ovsdb-tool.1.in \\\n-\tlib/common-syn.man \\\n-\tlib/common.man \\\n-\tlib/vlog-syn.man \\\n-\tlib/vlog.man \\\n-\tovsdb/ovsdb-schemas.man\n-ovsdb/ovsdb-tool.1.in:\n-lib/common-syn.man:\n-lib/common.man:\n-lib/vlog-syn.man:\n-lib/vlog.man:\n-ovsdb/ovsdb-schemas.man:\n-\n-utilities/bugtool/ovs-bugtool.8: \\\n-\tutilities/bugtool/ovs-bugtool.8.in\n-utilities/bugtool/ovs-bugtool.8.in:\n-\n-utilities/ovs-appctl.8: \\\n-\tutilities/ovs-appctl.8.in \\\n-\tlib/common.man\n-utilities/ovs-appctl.8.in:\n-lib/common.man:\n-\n-utilities/ovs-dpctl-top.8: \\\n-\tutilities/ovs-dpctl-top.8.in\n-utilities/ovs-dpctl-top.8.in:\n-\n-utilities/ovs-dpctl.8: \\\n-\tutilities/ovs-dpctl.8.in \\\n-\tlib/common.man \\\n-\tlib/dpctl.man \\\n-\tlib/vlog.man\n-utilities/ovs-dpctl.8.in:\n-lib/common.man:\n-lib/dpctl.man:\n-lib/vlog.man:\n-\n-utilities/ovs-l3ping.8: \\\n-\tutilities/ovs-l3ping.8.in \\\n-\tlib/common-syn.man \\\n-\tlib/common.man\n-utilities/ovs-l3ping.8.in:\n-lib/common-syn.man:\n-lib/common.man:\n-\n-utilities/ovs-ofctl.8: \\\n-\tutilities/ovs-ofctl.8.in \\\n-\tlib/colors.man \\\n-\tlib/common.man \\\n-\tlib/daemon.man \\\n-\tlib/ofp-version.man \\\n-\tlib/ssl.man \\\n-\tlib/unixctl.man \\\n-\tlib/vconn-active.man \\\n-\tlib/vlog.man\n-utilities/ovs-ofctl.8.in:\n-lib/colors.man:\n-lib/common.man:\n-lib/daemon.man:\n-lib/ofp-version.man:\n-lib/ssl.man:\n-lib/unixctl.man:\n-lib/vconn-active.man:\n-lib/vlog.man:\n-\n-utilities/ovs-pcap.1: \\\n-\tutilities/ovs-pcap.1.in \\\n-\tlib/common-syn.man \\\n-\tlib/common.man\n-utilities/ovs-pcap.1.in:\n-lib/common-syn.man:\n-lib/common.man:\n-\n-utilities/ovs-pki.8: \\\n-\tutilities/ovs-pki.8.in\n-utilities/ovs-pki.8.in:\n-\n-utilities/ovs-tcpdump.8: \\\n-\tutilities/ovs-tcpdump.8.in \\\n-\tlib/common.man\n-utilities/ovs-tcpdump.8.in:\n-lib/common.man:\n-\n-utilities/ovs-tcpundump.1: \\\n-\tutilities/ovs-tcpundump.1.in \\\n-\tlib/common-syn.man \\\n-\tlib/common.man\n-utilities/ovs-tcpundump.1.in:\n-lib/common-syn.man:\n-lib/common.man:\n-\n-utilities/ovs-testcontroller.8: \\\n-\tutilities/ovs-testcontroller.8.in \\\n-\tlib/common.man \\\n-\tlib/daemon.man \\\n-\tlib/ofp-version.man \\\n-\tlib/ssl-peer-ca-cert.man \\\n-\tlib/ssl.man \\\n-\tlib/unixctl.man \\\n-\tlib/vconn-active.man \\\n-\tlib/vconn-passive.man \\\n-\tlib/vlog.man\n-utilities/ovs-testcontroller.8.in:\n-lib/common.man:\n-lib/daemon.man:\n-lib/ofp-version.man:\n-lib/ssl-peer-ca-cert.man:\n-lib/ssl.man:\n-lib/unixctl.man:\n-lib/vconn-active.man:\n-lib/vconn-passive.man:\n-lib/vlog.man:\n-\n-utilities/ovs-vlan-bug-workaround.8: \\\n-\tutilities/ovs-vlan-bug-workaround.8.in \\\n-\tlib/common.man \\\n-\tutilities/ovs-vlan-bugs.man\n-utilities/ovs-vlan-bug-workaround.8.in:\n-lib/common.man:\n-utilities/ovs-vlan-bugs.man:\n-\n-utilities/ovs-vsctl.8: \\\n-\tutilities/ovs-vsctl.8.in \\\n-\tlib/common.man \\\n-\tlib/db-ctl-base.man \\\n-\tlib/ssl-bootstrap.man \\\n-\tlib/ssl-peer-ca-cert.man \\\n-\tlib/ssl.man \\\n-\tlib/table.man \\\n-\tlib/vconn-active.man \\\n-\tlib/vconn-passive.man \\\n-\tlib/vlog.man\n-utilities/ovs-vsctl.8.in:\n-lib/common.man:\n-lib/db-ctl-base.man:\n-lib/ssl-bootstrap.man:\n-lib/ssl-peer-ca-cert.man:\n-lib/ssl.man:\n-lib/table.man:\n-lib/vconn-active.man:\n-lib/vconn-passive.man:\n-lib/vlog.man:\n-\n-vswitchd/ovs-vswitchd.8: \\\n-\tvswitchd/ovs-vswitchd.8.in \\\n-\tlib/common.man \\\n-\tlib/coverage-unixctl.man \\\n-\tlib/daemon.man \\\n-\tlib/dpctl.man \\\n-\tlib/memory-unixctl.man \\\n-\tlib/service.man \\\n-\tlib/ssl-bootstrap.man \\\n-\tlib/ssl.man \\\n-\tlib/unixctl.man \\\n-\tlib/vlog-unixctl.man \\\n-\tlib/vlog.man \\\n-\tofproto/ofproto-dpif-unixctl.man \\\n-\tofproto/ofproto-tnl-unixctl.man \\\n-\tofproto/ofproto-unixctl.man\n-vswitchd/ovs-vswitchd.8.in:\n-lib/common.man:\n-lib/coverage-unixctl.man:\n-lib/daemon.man:\n-lib/dpctl.man:\n-lib/memory-unixctl.man:\n-lib/service.man:\n-lib/ssl-bootstrap.man:\n-lib/ssl.man:\n-lib/unixctl.man:\n-lib/vlog-unixctl.man:\n-lib/vlog.man:\n-ofproto/ofproto-dpif-unixctl.man:\n-ofproto/ofproto-tnl-unixctl.man:\n-ofproto/ofproto-unixctl.man:\n-\n-vtep/vtep-ctl.8: \\\n-\tvtep/vtep-ctl.8.in \\\n-\tlib/common.man \\\n-\tlib/db-ctl-base.man \\\n-\tlib/ssl-bootstrap.man \\\n-\tlib/ssl-peer-ca-cert.man \\\n-\tlib/ssl.man \\\n-\tlib/table.man \\\n-\tlib/vlog.man\n-vtep/vtep-ctl.8.in:\n-lib/common.man:\n-lib/db-ctl-base.man:\n-lib/ssl-bootstrap.man:\n-lib/ssl-peer-ca-cert.man:\n-lib/ssl.man:\n-lib/table.man:\n-lib/vlog.man:\ndiff --git a/ovsdb/file.c b/ovsdb/file.c\nindex 54e5df15a2cd..d33bce83a1ea 100644\n--- a/ovsdb/file.c\n+++ b/ovsdb/file.c\n@@ -572,6 +572,19 @@ ovsdb_file_txn_to_json(const struct ovsdb_txn *txn)\n return ftxn.json;\n }\n \n+struct json *\n+ovsdb_file_txn_annotate(struct json *json, const char *comment)\n+{\n+ if (!json) {\n+ json = json_object_create();\n+ }\n+ if (comment) {\n+ json_object_put_string(json, \"_comment\", comment);\n+ }\n+ json_object_put(json, \"_date\", json_integer_create(time_wall_msec()));\n+ return json;\n+}\n+\n struct ovsdb_error *\n ovsdb_file_commit(struct ovsdb_file *file,\n const struct ovsdb_txn *txn, bool durable)\n@@ -832,14 +845,7 @@ ovsdb_file_txn_commit(struct json *json, const char *comment,\n {\n struct ovsdb_error *error;\n \n- if (!json) {\n- json = json_object_create();\n- }\n- if (comment) {\n- json_object_put_string(json, \"_comment\", comment);\n- }\n- json_object_put(json, \"_date\", json_integer_create(time_wall_msec()));\n-\n+ json = ovsdb_file_txn_annotate(json, comment);\n error = ovsdb_log_write(log, json);\n json_destroy(json);\n if (error) {\ndiff --git a/ovsdb/file.h b/ovsdb/file.h\nindex 30f211c431dc..bc9b32cf6c33 100644\n--- a/ovsdb/file.h\n+++ b/ovsdb/file.h\n@@ -49,6 +49,8 @@ struct ovsdb_error *ovsdb_file_commit(struct ovsdb_file *,\n const struct ovsdb_txn *, bool durable);\n void ovsdb_file_destroy(struct ovsdb_file *);\n \n+struct json *ovsdb_file_txn_annotate(struct json *, const char *comment);\n+\n struct ovsdb_error *ovsdb_file_convert(const struct ovsdb_file *,\n const struct ovsdb_schema *)\n OVS_WARN_UNUSED_RESULT;\ndiff --git a/ovsdb/log.c b/ovsdb/log.c\nindex a223d30ac28b..adc14761cd8a 100644\n--- a/ovsdb/log.c\n+++ b/ovsdb/log.c\n@@ -24,6 +24,7 @@\n #include <sys/stat.h>\n #include <unistd.h>\n \n+#include \"openvswitch/dynamic-string.h\"\n #include \"openvswitch/json.h\"\n #include \"lockfile.h\"\n #include \"ovsdb.h\"\n@@ -352,16 +353,30 @@ ovsdb_log_unread(struct ovsdb_log *file)\n file->offset = file->prev_offset;\n }\n \n+void\n+ovsdb_log_compose_record(const struct json *json,\n+ const char *magic, struct ds *header, struct ds *data)\n+{\n+ ovs_assert(json->type == JSON_OBJECT || json->type == JSON_ARRAY);\n+ ovs_assert(!header->length);\n+ ovs_assert(!data->length);\n+\n+ /* Compose content. Add a new-line (replacing the null terminator) to make\n+ * the file easier to read, even though it has no semantic value. */\n+ json_to_ds(json, 0, data);\n+ ds_put_char(data, '\\n');\n+\n+ /* Compose header. */\n+ uint8_t sha1[SHA1_DIGEST_SIZE];\n+ sha1_bytes(data->string, data->length, sha1);\n+ ds_put_format(header, \"%s %\"PRIuSIZE\" \"SHA1_FMT\"\\n\",\n+ magic, data->length, SHA1_ARGS(sha1));\n+}\n+\n struct ovsdb_error *\n ovsdb_log_write(struct ovsdb_log *file, const struct json *json)\n {\n- uint8_t sha1[SHA1_DIGEST_SIZE];\n struct ovsdb_error *error;\n- char *json_string;\n- char header[128];\n- size_t length;\n-\n- json_string = NULL;\n \n if (file->mode == OVSDB_LOG_READ || file->write_error) {\n file->mode = OVSDB_LOG_WRITE;\n@@ -383,38 +398,31 @@ ovsdb_log_write(struct ovsdb_log *file, const struct json *json)\n goto error;\n }\n \n- /* Compose content. Add a new-line (replacing the null terminator) to make\n- * the file easier to read, even though it has no semantic value. */\n- json_string = json_to_string(json, 0);\n- length = strlen(json_string) + 1;\n- json_string[length - 1] = '\\n';\n-\n- /* Compose header. */\n- sha1_bytes(json_string, length, sha1);\n- snprintf(header, sizeof header, \"%s %\"PRIuSIZE\" \"SHA1_FMT\"\\n\",\n- file->magic, length, SHA1_ARGS(sha1));\n+ struct ds header = DS_EMPTY_INITIALIZER;\n+ struct ds data = DS_EMPTY_INITIALIZER;\n+ ovsdb_log_compose_record(json, file->magic, &header, &data);\n+ size_t total_length = header.length + data.length;\n \n /* Write. */\n- if (fwrite(header, strlen(header), 1, file->stream) != 1\n- || fwrite(json_string, length, 1, file->stream) != 1\n- || fflush(file->stream))\n- {\n- error = ovsdb_io_error(errno, \"%s: write failed\", file->name);\n-\n+ bool ok = (fwrite(header.string, header.length, 1, file->stream) == 1\n+ && fwrite(data.string, data.length, 1, file->stream) == 1\n+ && fflush(file->stream) == 0);\n+ ds_destroy(&header);\n+ ds_destroy(&data);\n+ if (!ok) {\n /* Remove any partially written data, ignoring errors since there is\n * nothing further we can do. */\n ignore(ftruncate(fileno(file->stream), file->offset));\n \n+ error = ovsdb_io_error(errno, \"%s: write failed\", file->name);\n goto error;\n }\n \n- file->offset += strlen(header) + length;\n- free(json_string);\n+ file->offset += total_length;\n return NULL;\n \n error:\n file->write_error = true;\n- free(json_string);\n return error;\n }\n \ndiff --git a/ovsdb/log.h b/ovsdb/log.h\nindex 439487ade12e..5be7eb91b165 100644\n--- a/ovsdb/log.h\n+++ b/ovsdb/log.h\n@@ -19,6 +19,7 @@\n #include <sys/types.h>\n #include \"compiler.h\"\n \n+struct ds;\n struct json;\n struct ovsdb_log;\n \n@@ -42,6 +43,9 @@ struct ovsdb_error *ovsdb_log_read(struct ovsdb_log *, struct json **)\n OVS_WARN_UNUSED_RESULT;\n void ovsdb_log_unread(struct ovsdb_log *);\n \n+void ovsdb_log_compose_record(const struct json *, const char *magic,\n+ struct ds *header, struct ds *data);\n+\n struct ovsdb_error *ovsdb_log_write(struct ovsdb_log *, const struct json *)\n OVS_WARN_UNUSED_RESULT;\n struct ovsdb_error *ovsdb_log_commit(struct ovsdb_log *)\ndiff --git a/ovsdb/ovsdb-client.1.in b/ovsdb/ovsdb-client.1.in\nindex e8e1c69eedd3..26f007258c09 100644\n--- a/ovsdb/ovsdb-client.1.in\n+++ b/ovsdb/ovsdb-client.1.in\n@@ -33,6 +33,9 @@ ovsdb\\-client \\- command-line interface to \\fBovsdb-server\\fR(1)\n \\fBovsdb\\-client \\fR[\\fIoptions\\fR] \\fBdump\\fI \\fR[\\fIserver\\fR] \\fR[\\fIdatabase\\fR]\\fR [\\fItable\\fR\n [\\fIcolumn\\fR...]]\n .br\n+\\fBovsdb\\-client \\fR[\\fIoptions\\fR]\n+\\fBbackup\\fI \\fR[\\fIserver\\fR] \\fR[\\fIdatabase\\fR] > \\fIsnapshot\\fR\n+.br\n \\fBovsdb\\-client \\fR[\\fIoptions\\fR] \\fBmonitor\\fI \\fR[\\fIserver\\fR] \\fR[\\fIdatabase\\fR] \\fItable\\fR\n [\\fIcolumn\\fR[\\fB,\\fIcolumn\\fR]...]...\n .br\n@@ -185,6 +188,21 @@ and prints it on stdout as a series of tables. If \\fItable\\fR is\n specified, only that table is retrieved. If at least one \\fIcolumn\\fR\n is specified, only those columns are retrieved.\n .\n+.IP \"\\fBbackup\\fI \\fR[\\fIserver\\fR] \\fR[\\fIdatabase\\fR] > \\fIsnapshot\\fR\"\n+Connects to \\fIserver\\fR, retrieves a snapshot of the schema and data\n+in \\fIdatabase\\fR, and prints it on stdout in the format used for\n+OVSDB standalone and active-backup database. This is an appropriate\n+way to back up a remote database. The database snapshot that it\n+outputs is suitable to be served up directly by \\fBovsdb\\-server\\fR or\n+used as the input to \\fBovsdb\\-client restore\\fR.\n+.IP\n+Another way to back up a standalone or active-backup database is to\n+copy its database file, e.g. with \\fBcp\\fR. This is safe even if the\n+database is in use.\n+.IP\n+The output does not include ephemeral columns, which by design do not\n+survive across restarts of \\fBovsdb\\-server\\fR.\n+.\n .IP \"\\fBmonitor\\fI \\fR[\\fIserver\\fR] \\fR[\\fIdatabase\\fR] \\fItable\\fR [\\fIcolumn\\fR[\\fB,\\fIcolumn\\fR]...]...\"\n .IQ \"\\fBmonitor\\-cond\\fI \\fR[\\fIserver\\fR] \\fR[\\fIdatabase\\fR] \\fIconditions\\fR \\fItable\\fR [\\fIcolumn\\fR[\\fB,\\fIcolumn\\fR]...]...\"\n Connects to \\fIserver\\fR and monitors the contents of rows that match conditions in\ndiff --git a/ovsdb/ovsdb-client.c b/ovsdb/ovsdb-client.c\nindex 7ef0716ea3af..bfffc08effb0 100644\n--- a/ovsdb/ovsdb-client.c\n+++ b/ovsdb/ovsdb-client.c\n@@ -32,9 +32,11 @@\n #include \"dirs.h\"\n #include \"openvswitch/dynamic-string.h\"\n #include \"fatal-signal.h\"\n+#include \"file.h\"\n #include \"openvswitch/json.h\"\n #include \"jsonrpc.h\"\n #include \"lib/table.h\"\n+#include \"log.h\"\n #include \"ovsdb.h\"\n #include \"ovsdb-data.h\"\n #include \"ovsdb-error.h\"\n@@ -300,6 +302,8 @@ usage(void)\n \" in DATBASE on SERVER.\\n\"\n \"\\n dump [SERVER] [DATABASE]\\n\"\n \" dump contents of DATABASE on SERVER to stdout\\n\"\n+ \"\\n backup [SERVER] [DATABASE] > DB\\n\"\n+ \" dump database contents in the form of a database file\\n\"\n \"\\n lock [SERVER] LOCK\\n\"\n \" create or wait for LOCK in SERVER\\n\"\n \"\\n steal [SERVER] LOCK\\n\"\n@@ -1474,6 +1478,135 @@ do_dump(struct jsonrpc *rpc, const char *database,\n }\n \n static void\n+print_and_free_log_record(struct json *record)\n+{\n+ struct ds header = DS_EMPTY_INITIALIZER;\n+ struct ds data = DS_EMPTY_INITIALIZER;\n+ ovsdb_log_compose_record(record, OVSDB_MAGIC, &header, &data);\n+ fwrite(header.string, header.length, 1, stdout);\n+ fwrite(data.string, data.length, 1, stdout);\n+ ds_destroy(&data);\n+ ds_destroy(&header);\n+ json_destroy(record);\n+}\n+\n+static void\n+do_backup(struct jsonrpc *rpc, const char *database,\n+ int argc OVS_UNUSED, char *argv[] OVS_UNUSED)\n+{\n+ if (isatty(STDOUT_FILENO)) {\n+ ovs_fatal(0, \"not writing backup to a terminal; \"\n+ \"please redirect stdout to a file\");\n+ }\n+\n+ /* Get schema. */\n+ struct ovsdb_schema *schema = fetch_schema(rpc, database);\n+\n+ /* Construct transaction to retrieve all tables. */\n+ struct json *txn = json_array_create_1(json_string_create(database));\n+ struct shash_node *node;\n+ SHASH_FOR_EACH (node, &schema->tables) {\n+ const char *table_name = node->name;\n+ const struct ovsdb_table_schema *table = node->data;\n+\n+ /* Get all the columns except _version and the ephemeral ones.\n+ *\n+ * We don't omit tables that only have ephemeral columns because of the\n+ * possibility that other tables references rows in those tables; that\n+ * is, even if all the columns are ephemeral, the rows themselves are\n+ * not. */\n+ struct json *columns = json_array_create_empty();\n+ struct shash_node *node2;\n+ SHASH_FOR_EACH (node2, &table->columns) {\n+ const struct ovsdb_column *column = node2->data;\n+\n+ if (column->persistent) {\n+ if (!columns) {\n+ columns = json_array_create_empty();\n+ }\n+ json_array_add(columns, json_string_create(column->name));\n+ }\n+ }\n+\n+ struct json *op = json_object_create();\n+ json_object_put_string(op, \"op\", \"select\");\n+ json_object_put_string(op, \"table\", table_name);\n+ json_object_put(op, \"where\", json_array_create_empty());\n+ json_object_put(op, \"columns\", columns);\n+ json_array_add(txn, op);\n+ }\n+\n+ /* Send request, get reply. */\n+ struct jsonrpc_msg *rq = jsonrpc_create_request(\"transact\", txn, NULL);\n+ struct jsonrpc_msg *reply;\n+ check_txn(jsonrpc_transact_block(rpc, rq, &reply), &reply);\n+\n+ /* Print schema record. */\n+ print_and_free_log_record(ovsdb_schema_to_json(schema));\n+\n+ /* Print database transaction record. */\n+ if (reply->result->type != JSON_ARRAY\n+ || reply->result->u.array.n != shash_count(&schema->tables)) {\n+ ovs_fatal(0, \"reply is not array of %\"PRIuSIZE\" elements: %s\",\n+ shash_count(&schema->tables),\n+ json_to_string(reply->result, 0));\n+ }\n+ struct json *output_txn = json_object_create();\n+\n+ size_t i = 0;\n+ SHASH_FOR_EACH (node, &schema->tables) {\n+ const char *table_name = node->name;\n+ const struct ovsdb_table_schema *table = node->data;\n+ const struct json *op_result = reply->result->u.array.elems[i++];\n+ struct json *rows;\n+\n+ if (op_result->type != JSON_OBJECT\n+ || !(rows = shash_find_data(json_object(op_result), \"rows\"))\n+ || rows->type != JSON_ARRAY) {\n+ ovs_fatal(0, \"%s table reply is not an object with a \\\"rows\\\" \"\n+ \"member array: %s\",\n+ table->name, json_to_string(op_result, 0));\n+ }\n+\n+ if (!rows->u.array.n) {\n+ continue;\n+ }\n+\n+ struct json *output_rows = json_object_create();\n+ for (size_t j = 0; j < rows->u.array.n; j++) {\n+ struct json *row = rows->u.array.elems[j];\n+ if (row->type != JSON_OBJECT) {\n+ ovs_fatal(0, \"%s table reply row is not an object: %s\",\n+ table_name, json_to_string(row, 0));\n+ }\n+\n+ struct json *uuid_json = shash_find_and_delete(json_object(row),\n+ \"_uuid\");\n+ if (!uuid_json) {\n+ ovs_fatal(0, \"%s table reply row lacks _uuid member: %s\",\n+ table_name, json_to_string(row, 0));\n+ }\n+\n+ const struct ovsdb_base_type uuid_base = OVSDB_BASE_UUID_INIT;\n+ union ovsdb_atom atom;\n+ check_ovsdb_error(ovsdb_atom_from_json(&atom, &uuid_base,\n+ uuid_json, NULL));\n+\n+ char uuid_s[UUID_LEN + 1];\n+ snprintf(uuid_s, sizeof uuid_s, UUID_FMT, UUID_ARGS(&atom.uuid));\n+ json_object_put(output_rows, uuid_s, json_clone(row));\n+ }\n+ json_object_put(output_txn, table_name, output_rows);\n+ }\n+ output_txn = ovsdb_file_txn_annotate(\n+ output_txn, \"produced by \\\"ovsdb-client backup\\\"\");\n+ print_and_free_log_record(output_txn);\n+\n+ ovsdb_schema_destroy(schema);\n+ jsonrpc_msg_destroy(reply);\n+}\n+\n+static void\n do_help(struct jsonrpc *rpc OVS_UNUSED, const char *database OVS_UNUSED,\n int argc OVS_UNUSED, char *argv[] OVS_UNUSED)\n {\n@@ -1687,6 +1820,7 @@ static const struct ovsdb_client_command all_commands[] = {\n { \"convert\", NEED_RPC, 1, 1, do_convert },\n { \"needs-conversion\", NEED_RPC, 1, 1, do_needs_conversion },\n { \"dump\", NEED_DATABASE, 0, INT_MAX, do_dump },\n+ { \"backup\", NEED_DATABASE, 0, 0, do_backup },\n { \"lock\", NEED_RPC, 1, 1, do_lock_create },\n { \"steal\", NEED_RPC, 1, 1, do_lock_steal },\n { \"unlock\", NEED_RPC, 1, 1, do_lock_unlock },\ndiff --git a/ovsdb/ovsdb.7.xml b/ovsdb/ovsdb.7.xml\nindex efd7a622e2e0..5461f252a03a 100644\n--- a/ovsdb/ovsdb.7.xml\n+++ b/ovsdb/ovsdb.7.xml\n@@ -404,6 +404,12 @@\n </p>\n \n <p>\n+ Another way to make a backup is to use <code>ovsdb-client backup</code>,\n+ which connects to a running database server and outputs an atomic snapshot\n+ of its schema and content, in the same format used for on-disk databases.\n+ </p>\n+\n+ <p>\n To restore from a backup, stop the database server or servers, overwrite\n the database file with the backup (e.g. with <code>cp</code>), and then\n restart the servers.\ndiff --git a/tests/ovsdb-client.at b/tests/ovsdb-client.at\nindex 3bce96b23fc8..18dcba8188ae 100644\n--- a/tests/ovsdb-client.at\n+++ b/tests/ovsdb-client.at\n@@ -1,7 +1,7 @@\n AT_BANNER([OVSDB -- ovsdb-client commands])\n \n AT_SETUP([ovsdb-client get-schema-version])\n-AT_KEYWORDS([ovsdb server positive])\n+AT_KEYWORDS([ovsdb client positive])\n ordinal_schema > schema\n AT_CHECK([ovsdb-tool create db schema], [0], [ignore], [ignore])\n AT_CHECK([ovsdb-server --detach --no-chdir --pidfile --remote=punix:socket db], [0], [ignore], [ignore])\n@@ -11,7 +11,7 @@ OVSDB_SERVER_SHUTDOWN\n AT_CLEANUP\n \n AT_SETUP([ovsdb-client get-schema-version - tcp socket])\n-AT_KEYWORDS([ovsdb server positive tcp])\n+AT_KEYWORDS([ovsdb client positive tcp])\n ordinal_schema > schema\n AT_CHECK([ovsdb-tool create db schema], [0], [ignore], [ignore])\n AT_CHECK([ovsdb-server --log-file --detach --no-chdir --pidfile --remote=ptcp:0:127.0.0.1 db], [0], [ignore], [ignore])\n@@ -20,3 +20,54 @@ AT_CHECK([ovsdb-client get-schema-version tcp:127.0.0.1:$TCP_PORT ordinals], [0]\n ])\n OVSDB_SERVER_SHUTDOWN\n AT_CLEANUP])\n+\n+AT_SETUP([ovsdb-client backup])\n+AT_KEYWORDS([ovsdb client positive])\n+\n+on_exit 'kill `cat *.pid`'\n+\n+dnl Create a database.\n+ordinal_schema > schema\n+touch .db.~lock~\n+AT_CHECK([ovsdb-tool create db schema])\n+\n+dnl Put some data in the database.\n+AT_CHECK(\n+ [[for pair in 'zero 0' 'one 1' 'two 2' 'three 3' 'four 4' 'five 5'; do\n+ set -- $pair\n+ ovsdb-tool transact db '\n+ [\"ordinals\",\n+ {\"op\": \"insert\",\n+ \"table\": \"ordinals\",\n+ \"row\": {\"name\": \"'$1'\", \"number\": '$2'}},\n+ {\"op\": \"comment\",\n+ \"comment\": \"add row for '\"$pair\"'\"}]'\n+ done | ${PERL} $srcdir/uuidfilt.pl]], [0],\n+[[[{\"uuid\":[\"uuid\",\"<0>\"]},{}]\n+[{\"uuid\":[\"uuid\",\"<1>\"]},{}]\n+[{\"uuid\":[\"uuid\",\"<2>\"]},{}]\n+[{\"uuid\":[\"uuid\",\"<3>\"]},{}]\n+[{\"uuid\":[\"uuid\",\"<4>\"]},{}]\n+[{\"uuid\":[\"uuid\",\"<5>\"]},{}]\n+]], [ignore])\n+\n+dnl Start the database server.\n+AT_CHECK([ovsdb-server -vfile -vvlog:off --detach --no-chdir --pidfile --log-file --remote=punix:db.sock db], [0])\n+AT_CAPTURE_FILE([ovsdb-server.log])\n+\n+dnl Dump a copy of the data and a backup of it.\n+AT_CHECK([ovsdb-client dump > dump1])\n+AT_CHECK([ovsdb-client backup > backup])\n+\n+dnl Stop the database server, then re-start it based on the backup.\n+OVS_APP_EXIT_AND_WAIT([ovsdb-server])\n+AT_CHECK([ovsdb-server -vfile -vvlog:off --detach --no-chdir --pidfile --log-file --remote=punix:db.sock backup], [0])\n+\n+dnl Dump a new copy of the data.\n+AT_CHECK([ovsdb-client dump > dump2])\n+sort dump2 > expout\n+\n+dnl Verify that the two dumps are the same.\n+AT_CHECK([sort dump1], [0], [expout])\n+\n+AT_CLEANUP\n", "prefixes": [ "ovs-dev", "RFC", "48/52" ] }