bf251437e9
Many changes were necessary this time around: * QAPI was changed to avoid redundant has_* variables, see commit 44ea9d9be3 ("qapi: Start to elide redundant has_FOO in generated C") for details. This affected many QMP commands added by Proxmox too. * Pending querying for migration got split into two functions, one to estimate, one for exact value, see commit c8df4a7aef ("migration: Split save_live_pending() into state_pending_*") for details. Relevant for savevm-async and PBS dirty bitmap. * Some block (driver) functions got converted to coroutines, so the Proxmox block drivers needed to be adapted. * Alloc track auto-detaching during PBS live restore got broken by AioContext-related changes resulting in a deadlock. The current, hacky method was replaced by a simpler one. Stefan apparently ran into a problem with that when he wrote the driver, but there were improvements in the stream job code since then and I didn't manage to reproduce the issue. It's a separate patch "alloc-track: fix deadlock during drop" for now, you can find the details there. * Async snapshot-related changes: - The pending querying got adapted to the above-mentioned split and a patch is added to optimize it/make it more similar to what upstream code does. - Added initialization of the compression counters (for future-proofing). - It's necessary the hold the BQL (big QEMU lock = iothread mutex) during the setup phase, because block layer functions are used there and not doing so leads to racy, hard-to-debug crashes or hangs. It's necessary to change some upstream code too for this, a version of the patch "migration: for snapshots, hold the BQL during setup callbacks" is intended to be upstreamed. - Need to take the bdrv graph read lock before flushing. * hmp_info_balloon was moved to a different file. * Needed to include a new headers from time to time to still get the correct functions. Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
425 lines
14 KiB
Diff
425 lines
14 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: Stefan Reiter <s.reiter@proxmox.com>
|
|
Date: Wed, 8 Jul 2020 09:50:54 +0200
|
|
Subject: [PATCH] PVE: Add PBS block driver to map backup archives into VMs
|
|
|
|
Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
|
|
[error cleanups, file_open implementation]
|
|
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
|
|
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
|
|
[FE: adapt to changed function signatures
|
|
make pbs_co_preadv return values consistent with QEMU
|
|
getlength is now a coroutine function]
|
|
Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
|
|
---
|
|
block/meson.build | 3 +
|
|
block/pbs.c | 277 +++++++++++++++++++++++++++++++++++++++++++
|
|
configure | 9 ++
|
|
meson.build | 2 +-
|
|
qapi/block-core.json | 13 ++
|
|
qapi/pragma.json | 1 +
|
|
6 files changed, 304 insertions(+), 1 deletion(-)
|
|
create mode 100644 block/pbs.c
|
|
|
|
diff --git a/block/meson.build b/block/meson.build
|
|
index 5bcebb934b..eece0d5743 100644
|
|
--- a/block/meson.build
|
|
+++ b/block/meson.build
|
|
@@ -54,6 +54,9 @@ block_ss.add(files(
|
|
'../pve-backup.c',
|
|
), libproxmox_backup_qemu)
|
|
|
|
+block_ss.add(when: 'CONFIG_PBS_BDRV', if_true: files('pbs.c'))
|
|
+block_ss.add(when: 'CONFIG_PBS_BDRV', if_true: libproxmox_backup_qemu)
|
|
+
|
|
|
|
softmmu_ss.add(when: 'CONFIG_TCG', if_true: files('blkreplay.c'))
|
|
softmmu_ss.add(files('block-ram-registrar.c'))
|
|
diff --git a/block/pbs.c b/block/pbs.c
|
|
new file mode 100644
|
|
index 0000000000..43e69ada46
|
|
--- /dev/null
|
|
+++ b/block/pbs.c
|
|
@@ -0,0 +1,277 @@
|
|
+/*
|
|
+ * Proxmox Backup Server read-only block driver
|
|
+ */
|
|
+
|
|
+#include "qemu/osdep.h"
|
|
+#include "qapi/error.h"
|
|
+#include "qapi/qmp/qdict.h"
|
|
+#include "qapi/qmp/qstring.h"
|
|
+#include "qemu/module.h"
|
|
+#include "qemu/option.h"
|
|
+#include "qemu/cutils.h"
|
|
+#include "block/block_int.h"
|
|
+#include "block/block-io.h"
|
|
+
|
|
+#include <proxmox-backup-qemu.h>
|
|
+
|
|
+#define PBS_OPT_REPOSITORY "repository"
|
|
+#define PBS_OPT_SNAPSHOT "snapshot"
|
|
+#define PBS_OPT_ARCHIVE "archive"
|
|
+#define PBS_OPT_KEYFILE "keyfile"
|
|
+#define PBS_OPT_PASSWORD "password"
|
|
+#define PBS_OPT_FINGERPRINT "fingerprint"
|
|
+#define PBS_OPT_ENCRYPTION_PASSWORD "key_password"
|
|
+
|
|
+typedef struct {
|
|
+ ProxmoxRestoreHandle *conn;
|
|
+ char aid;
|
|
+ int64_t length;
|
|
+
|
|
+ char *repository;
|
|
+ char *snapshot;
|
|
+ char *archive;
|
|
+} BDRVPBSState;
|
|
+
|
|
+static QemuOptsList runtime_opts = {
|
|
+ .name = "pbs",
|
|
+ .head = QTAILQ_HEAD_INITIALIZER(runtime_opts.head),
|
|
+ .desc = {
|
|
+ {
|
|
+ .name = PBS_OPT_REPOSITORY,
|
|
+ .type = QEMU_OPT_STRING,
|
|
+ .help = "The server address and repository to connect to.",
|
|
+ },
|
|
+ {
|
|
+ .name = PBS_OPT_SNAPSHOT,
|
|
+ .type = QEMU_OPT_STRING,
|
|
+ .help = "The snapshot to read.",
|
|
+ },
|
|
+ {
|
|
+ .name = PBS_OPT_ARCHIVE,
|
|
+ .type = QEMU_OPT_STRING,
|
|
+ .help = "Which archive within the snapshot should be accessed.",
|
|
+ },
|
|
+ {
|
|
+ .name = PBS_OPT_PASSWORD,
|
|
+ .type = QEMU_OPT_STRING,
|
|
+ .help = "Server password. Can be passed as env var 'PBS_PASSWORD'.",
|
|
+ },
|
|
+ {
|
|
+ .name = PBS_OPT_FINGERPRINT,
|
|
+ .type = QEMU_OPT_STRING,
|
|
+ .help = "Server fingerprint. Can be passed as env var 'PBS_FINGERPRINT'.",
|
|
+ },
|
|
+ {
|
|
+ .name = PBS_OPT_ENCRYPTION_PASSWORD,
|
|
+ .type = QEMU_OPT_STRING,
|
|
+ .help = "Optional: Key password. Can be passed as env var 'PBS_ENCRYPTION_PASSWORD'.",
|
|
+ },
|
|
+ {
|
|
+ .name = PBS_OPT_KEYFILE,
|
|
+ .type = QEMU_OPT_STRING,
|
|
+ .help = "Optional: The path to the keyfile to use.",
|
|
+ },
|
|
+ { /* end of list */ }
|
|
+ },
|
|
+};
|
|
+
|
|
+
|
|
+// filename format:
|
|
+// pbs:repository=<repo>,snapshot=<snap>,password=<pw>,key_password=<kpw>,fingerprint=<fp>,archive=<archive>
|
|
+static void pbs_parse_filename(const char *filename, QDict *options,
|
|
+ Error **errp)
|
|
+{
|
|
+
|
|
+ if (!strstart(filename, "pbs:", &filename)) {
|
|
+ if (errp) error_setg(errp, "pbs_parse_filename failed - missing 'pbs:' prefix");
|
|
+ }
|
|
+
|
|
+
|
|
+ QemuOpts *opts = qemu_opts_parse_noisily(&runtime_opts, filename, false);
|
|
+ if (!opts) {
|
|
+ if (errp) error_setg(errp, "pbs_parse_filename failed");
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ qemu_opts_to_qdict(opts, options);
|
|
+
|
|
+ qemu_opts_del(opts);
|
|
+}
|
|
+
|
|
+static int pbs_open(BlockDriverState *bs, QDict *options, int flags,
|
|
+ Error **errp)
|
|
+{
|
|
+ QemuOpts *opts;
|
|
+ BDRVPBSState *s = bs->opaque;
|
|
+ char *pbs_error = NULL;
|
|
+
|
|
+ opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort);
|
|
+ qemu_opts_absorb_qdict(opts, options, &error_abort);
|
|
+
|
|
+ s->repository = g_strdup(qemu_opt_get(opts, PBS_OPT_REPOSITORY));
|
|
+ s->snapshot = g_strdup(qemu_opt_get(opts, PBS_OPT_SNAPSHOT));
|
|
+ s->archive = g_strdup(qemu_opt_get(opts, PBS_OPT_ARCHIVE));
|
|
+ const char *keyfile = qemu_opt_get(opts, PBS_OPT_KEYFILE);
|
|
+ const char *password = qemu_opt_get(opts, PBS_OPT_PASSWORD);
|
|
+ const char *fingerprint = qemu_opt_get(opts, PBS_OPT_FINGERPRINT);
|
|
+ const char *key_password = qemu_opt_get(opts, PBS_OPT_ENCRYPTION_PASSWORD);
|
|
+
|
|
+ if (!password) {
|
|
+ password = getenv("PBS_PASSWORD");
|
|
+ }
|
|
+ if (!fingerprint) {
|
|
+ fingerprint = getenv("PBS_FINGERPRINT");
|
|
+ }
|
|
+ if (!key_password) {
|
|
+ key_password = getenv("PBS_ENCRYPTION_PASSWORD");
|
|
+ }
|
|
+
|
|
+ /* connect to PBS server in read mode */
|
|
+ s->conn = proxmox_restore_new(s->repository, s->snapshot, password,
|
|
+ keyfile, key_password, fingerprint, &pbs_error);
|
|
+
|
|
+ /* invalidates qemu_opt_get char pointers from above */
|
|
+ qemu_opts_del(opts);
|
|
+
|
|
+ if (!s->conn) {
|
|
+ if (pbs_error && errp) error_setg(errp, "PBS restore_new failed: %s", pbs_error);
|
|
+ if (pbs_error) proxmox_backup_free_error(pbs_error);
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ int ret = proxmox_restore_connect(s->conn, &pbs_error);
|
|
+ if (ret < 0) {
|
|
+ if (pbs_error && errp) error_setg(errp, "PBS connect failed: %s", pbs_error);
|
|
+ if (pbs_error) proxmox_backup_free_error(pbs_error);
|
|
+ return -ECONNREFUSED;
|
|
+ }
|
|
+
|
|
+ /* acquire handle and length */
|
|
+ s->aid = proxmox_restore_open_image(s->conn, s->archive, &pbs_error);
|
|
+ if (s->aid < 0) {
|
|
+ if (pbs_error && errp) error_setg(errp, "PBS open_image failed: %s", pbs_error);
|
|
+ if (pbs_error) proxmox_backup_free_error(pbs_error);
|
|
+ return -ENODEV;
|
|
+ }
|
|
+ s->length = proxmox_restore_get_image_length(s->conn, s->aid, &pbs_error);
|
|
+ if (s->length < 0) {
|
|
+ if (pbs_error && errp) error_setg(errp, "PBS get_image_length failed: %s", pbs_error);
|
|
+ if (pbs_error) proxmox_backup_free_error(pbs_error);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int pbs_file_open(BlockDriverState *bs, QDict *options, int flags,
|
|
+ Error **errp)
|
|
+{
|
|
+ return pbs_open(bs, options, flags, errp);
|
|
+}
|
|
+
|
|
+static void pbs_close(BlockDriverState *bs) {
|
|
+ BDRVPBSState *s = bs->opaque;
|
|
+ g_free(s->repository);
|
|
+ g_free(s->snapshot);
|
|
+ g_free(s->archive);
|
|
+ proxmox_restore_disconnect(s->conn);
|
|
+}
|
|
+
|
|
+static coroutine_fn int64_t pbs_co_getlength(BlockDriverState *bs)
|
|
+{
|
|
+ BDRVPBSState *s = bs->opaque;
|
|
+ return s->length;
|
|
+}
|
|
+
|
|
+typedef struct ReadCallbackData {
|
|
+ Coroutine *co;
|
|
+ AioContext *ctx;
|
|
+} ReadCallbackData;
|
|
+
|
|
+static void read_callback(void *callback_data)
|
|
+{
|
|
+ ReadCallbackData *rcb = callback_data;
|
|
+ aio_co_schedule(rcb->ctx, rcb->co);
|
|
+}
|
|
+
|
|
+static coroutine_fn int pbs_co_preadv(BlockDriverState *bs,
|
|
+ int64_t offset, int64_t bytes,
|
|
+ QEMUIOVector *qiov, BdrvRequestFlags flags)
|
|
+{
|
|
+ BDRVPBSState *s = bs->opaque;
|
|
+ int ret;
|
|
+ char *pbs_error = NULL;
|
|
+ uint8_t *buf = malloc(bytes);
|
|
+
|
|
+ if (offset < 0 || bytes < 0) {
|
|
+ fprintf(stderr, "unexpected negative 'offset' or 'bytes' value!\n");
|
|
+ return -EIO;
|
|
+ }
|
|
+
|
|
+ ReadCallbackData rcb = {
|
|
+ .co = qemu_coroutine_self(),
|
|
+ .ctx = bdrv_get_aio_context(bs),
|
|
+ };
|
|
+
|
|
+ proxmox_restore_read_image_at_async(s->conn, s->aid, buf, (uint64_t)offset, (uint64_t)bytes,
|
|
+ read_callback, (void *) &rcb, &ret, &pbs_error);
|
|
+
|
|
+ qemu_coroutine_yield();
|
|
+
|
|
+ if (ret < 0) {
|
|
+ fprintf(stderr, "error during PBS read: %s\n", pbs_error ? pbs_error : "unknown error");
|
|
+ if (pbs_error) proxmox_backup_free_error(pbs_error);
|
|
+ return -EIO;
|
|
+ }
|
|
+
|
|
+ qemu_iovec_from_buf(qiov, 0, buf, bytes);
|
|
+ free(buf);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static coroutine_fn int pbs_co_pwritev(BlockDriverState *bs,
|
|
+ int64_t offset, int64_t bytes,
|
|
+ QEMUIOVector *qiov, BdrvRequestFlags flags)
|
|
+{
|
|
+ fprintf(stderr, "pbs-bdrv: cannot write to backup file, make sure "
|
|
+ "any attached disk devices are set to read-only!\n");
|
|
+ return -EPERM;
|
|
+}
|
|
+
|
|
+static void pbs_refresh_filename(BlockDriverState *bs)
|
|
+{
|
|
+ BDRVPBSState *s = bs->opaque;
|
|
+ snprintf(bs->exact_filename, sizeof(bs->exact_filename), "%s/%s(%s)",
|
|
+ s->repository, s->snapshot, s->archive);
|
|
+}
|
|
+
|
|
+static const char *const pbs_strong_runtime_opts[] = {
|
|
+ NULL
|
|
+};
|
|
+
|
|
+static BlockDriver bdrv_pbs_co = {
|
|
+ .format_name = "pbs",
|
|
+ .protocol_name = "pbs",
|
|
+ .instance_size = sizeof(BDRVPBSState),
|
|
+
|
|
+ .bdrv_parse_filename = pbs_parse_filename,
|
|
+
|
|
+ .bdrv_file_open = pbs_file_open,
|
|
+ .bdrv_open = pbs_open,
|
|
+ .bdrv_close = pbs_close,
|
|
+ .bdrv_co_getlength = pbs_co_getlength,
|
|
+
|
|
+ .bdrv_co_preadv = pbs_co_preadv,
|
|
+ .bdrv_co_pwritev = pbs_co_pwritev,
|
|
+
|
|
+ .bdrv_refresh_filename = pbs_refresh_filename,
|
|
+ .strong_runtime_opts = pbs_strong_runtime_opts,
|
|
+};
|
|
+
|
|
+static void bdrv_pbs_init(void)
|
|
+{
|
|
+ bdrv_register(&bdrv_pbs_co);
|
|
+}
|
|
+
|
|
+block_init(bdrv_pbs_init);
|
|
diff --git a/configure b/configure
|
|
index 800b5850f4..37e12a3dce 100755
|
|
--- a/configure
|
|
+++ b/configure
|
|
@@ -288,6 +288,7 @@ linux_user=""
|
|
bsd_user=""
|
|
pie=""
|
|
coroutine=""
|
|
+pbs_bdrv="yes"
|
|
plugins="$default_feature"
|
|
meson=""
|
|
ninja=""
|
|
@@ -872,6 +873,10 @@ for opt do
|
|
;;
|
|
--with-coroutine=*) coroutine="$optarg"
|
|
;;
|
|
+ --disable-pbs-bdrv) pbs_bdrv="no"
|
|
+ ;;
|
|
+ --enable-pbs-bdrv) pbs_bdrv="yes"
|
|
+ ;;
|
|
--with-git=*) git="$optarg"
|
|
;;
|
|
--with-git-submodules=*)
|
|
@@ -1048,6 +1053,7 @@ cat << EOF
|
|
debug-info debugging information
|
|
safe-stack SafeStack Stack Smash Protection. Depends on
|
|
clang/llvm and requires coroutine backend ucontext.
|
|
+ pbs-bdrv Proxmox backup server read-only block driver support
|
|
|
|
NOTE: The object files are built at the place where configure is launched
|
|
EOF
|
|
@@ -2385,6 +2391,9 @@ echo "TARGET_DIRS=$target_list" >> $config_host_mak
|
|
if test "$modules" = "yes"; then
|
|
echo "CONFIG_MODULES=y" >> $config_host_mak
|
|
fi
|
|
+if test "$pbs_bdrv" = "yes" ; then
|
|
+ echo "CONFIG_PBS_BDRV=y" >> $config_host_mak
|
|
+fi
|
|
|
|
# XXX: suppress that
|
|
if [ "$bsd" = "yes" ] ; then
|
|
diff --git a/meson.build b/meson.build
|
|
index 8ec21bba90..419bea5cf4 100644
|
|
--- a/meson.build
|
|
+++ b/meson.build
|
|
@@ -4035,7 +4035,7 @@ summary_info += {'bzip2 support': libbzip2}
|
|
summary_info += {'lzfse support': liblzfse}
|
|
summary_info += {'zstd support': zstd}
|
|
summary_info += {'NUMA host support': numa}
|
|
-summary_info += {'capstone': capstone}
|
|
+summary_info += {'PBS bdrv support': config_host.has_key('CONFIG_PBS_BDRV')}
|
|
summary_info += {'libpmem support': libpmem}
|
|
summary_info += {'libdaxctl support': libdaxctl}
|
|
summary_info += {'libudev': libudev}
|
|
diff --git a/qapi/block-core.json b/qapi/block-core.json
|
|
index 864b8ce97c..705a65ab1a 100644
|
|
--- a/qapi/block-core.json
|
|
+++ b/qapi/block-core.json
|
|
@@ -3198,6 +3198,7 @@
|
|
'parallels', 'preallocate', 'qcow', 'qcow2', 'qed', 'quorum',
|
|
'raw', 'rbd',
|
|
{ 'name': 'replication', 'if': 'CONFIG_REPLICATION' },
|
|
+ 'pbs',
|
|
'ssh', 'throttle', 'vdi', 'vhdx',
|
|
{ 'name': 'virtio-blk-vfio-pci', 'if': 'CONFIG_BLKIO' },
|
|
{ 'name': 'virtio-blk-vhost-user', 'if': 'CONFIG_BLKIO' },
|
|
@@ -3274,6 +3275,17 @@
|
|
{ 'struct': 'BlockdevOptionsNull',
|
|
'data': { '*size': 'int', '*latency-ns': 'uint64', '*read-zeroes': 'bool' } }
|
|
|
|
+##
|
|
+# @BlockdevOptionsPbs:
|
|
+#
|
|
+# Driver specific block device options for the PBS backend.
|
|
+#
|
|
+##
|
|
+{ 'struct': 'BlockdevOptionsPbs',
|
|
+ 'data': { 'repository': 'str', 'snapshot': 'str', 'archive': 'str',
|
|
+ '*keyfile': 'str', '*password': 'str', '*fingerprint': 'str',
|
|
+ '*key_password': 'str' } }
|
|
+
|
|
##
|
|
# @BlockdevOptionsNVMe:
|
|
#
|
|
@@ -4647,6 +4659,7 @@
|
|
'nfs': 'BlockdevOptionsNfs',
|
|
'null-aio': 'BlockdevOptionsNull',
|
|
'null-co': 'BlockdevOptionsNull',
|
|
+ 'pbs': 'BlockdevOptionsPbs',
|
|
'nvme': 'BlockdevOptionsNVMe',
|
|
'nvme-io_uring': { 'type': 'BlockdevOptionsNvmeIoUring',
|
|
'if': 'CONFIG_BLKIO' },
|
|
diff --git a/qapi/pragma.json b/qapi/pragma.json
|
|
index f2097b9020..5ab1890519 100644
|
|
--- a/qapi/pragma.json
|
|
+++ b/qapi/pragma.json
|
|
@@ -47,6 +47,7 @@
|
|
'BlockInfo', # query-block
|
|
'BlockdevAioOptions', # blockdev-add, -blockdev
|
|
'BlockdevDriver', # blockdev-add, query-blockstats, ...
|
|
+ 'BlockdevOptionsPbs', # for PBS backwards compat
|
|
'BlockdevVmdkAdapterType', # blockdev-create (to match VMDK spec)
|
|
'BlockdevVmdkSubformat', # blockdev-create (to match VMDK spec)
|
|
'ColoCompareProperties', # object_add, -object
|