[libvirt] [PATCH RFC 00/40] qemu: Add support for incremental backups

This series is my attempt to clean up and finish incremental backup as it was last posted in v10 here: https://www.redhat.com/archives/libvir-list/2019-August/msg01018.html Out of that series, patch 1 was dropped for now and I'll revisit it later. The patches adding docs, xml parsing and job plumbing were partially modified and stripped of currently unimplemented functionality. The qemu implementation was completely rewritten. Of the new series first 28 patches are cleanups and can be reviewed for pushing. The main incremental backup functionality is not complete yet and should not be pushed right now not even in experimental (disabled [1]) state due to possible API changes: - figure out whether we want our own event for the backup job or want to deal with the domain job event - if we go with the domain job event everything must be rewritten as a single domain job - otherwise plumbing for the event must be added - documentation must reflect the above after that will be sorted out the following needs to be done until we can finally enable it: - more testing. I merely verified that qemu generates files/exports NBD - blockjob support for bitmaps (for commit/pull/copy) - snapshot support for bitmaps - merging of bitmaps accross backing chain entries - figuring out a more elegant way to do the bitmap for the backup than creating two, one for the 'store' (for NBD server) and one for the disk itself. - ... Until then the following XML can be used to enable the functionality for testing without any guarantee [1]: <domain type='kvm' xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'> ... <qemu:capabilities> <qemu:add capability='blockdev'/> <qemu:add capability='incremental-backup'/> </qemu:capabilities> </domain> Eric Blake (5): backup: Document new XML for backups backup: Introduce virDomainBackup APIs backup: Implement backup APIs for remote driver backup: Parse and output backup XML backup: Implement virsh support for backup Peter Krempa (35): util: hash: Add possibility to use simpler data free function in virHash util: hash: Add new constructor 'virHashNew' util: hash: Introduce virHashHasEntry qemu: domain: Split out setup of virStorageSource from qemu driver config qemu: domain: Remove pointless return value in qemuDomainPrepareDiskSourceData qemu: domain: clarify sematics of qemuDomainPrepareDiskSourceData qemu: domain: Tolerate NULL @disk in qemuDomainPrepareDiskSourceData conf: Remove virDomainDiskPathByName conf: Introduce virDomainDiskByTarget qemu: Replace use of virDomainDiskFindByBusAndDst with virDomainDiskByTarget conf: Remove unused virDomainDiskFindByBusAndDst Replace virDomainDiskByName by virDomainDiskByTarget in appropriate cases qemu: monitor: Introduce new interface to query-named-block-nodes qemu: block: Don't query monitor in qemuBlockStorageSourceCreateDetectSize qemu: checkpoint: Fix rollback and access to unlocked 'vm' when deleting checkpoints qemu: monitor: Remove non-transaction based dirty bitmap APIs conf: snapshot: Don't clear current snapshot when redefining an existing one conf: snapshot: Remove 'update_current' parameter from virDomainSnapshotRedefinePrep conf: Don't reuse variable for different object in virDomainCheckpointRedefinePrep conf: checkpoint: Don't clear current checkpoint when redefining an existing one conf: checkpoint: Don't clear current checkpoint when redefining qemu: checkpoint: Enforce that 'bitmap' name must match checkpoint name qemu: checkpoint: Split out checkpoint creation code qemu: checkpoint: Extract finalizing steps of checkpoint creation qemu: monitor: Add helper for generating data for block bitmap merging qemu: checkpoint: Use qemuMonitorTransactionBitmapMergeSourceAddBitmap qemu: blockjob: Refactor qemuBlockJobEventProcessConcludedTransition qemu: blockjob: Use 'g_free' in qemuBlockJobDataDispose Add 'backup' block job type qemu: monitor: Add support for blockdev-backup via 'transaction' qemu: domain: Track backup job data in the status XML qemu: blockjob: Track internal data for 'backup' blockjob tests: qemustatusxml2xml: Add test for 'pull' type backup job qemu: Implement backup job APIs and qemu handling qemu: blockjob: Implement concluded blockjob handler for backup blockjobs docs/docs.html.in | 3 +- docs/format.html.in | 1 + docs/formatbackup.html.in | 168 ++++ docs/formatcheckpoint.html.in | 12 +- docs/index.html.in | 3 +- docs/schemas/domainbackup.rng | 219 +++++ examples/c/misc/event-test.c | 3 + include/libvirt/libvirt-domain.h | 29 +- libvirt.spec.in | 1 + mingw-libvirt.spec.in | 2 + src/conf/Makefile.inc.am | 2 + src/conf/backup_conf.c | 518 ++++++++++ src/conf/backup_conf.h | 102 ++ src/conf/checkpoint_conf.c | 17 +- src/conf/domain_addr.c | 4 +- src/conf/domain_conf.c | 50 +- src/conf/domain_conf.h | 8 +- src/conf/snapshot_conf.c | 6 - src/conf/snapshot_conf.h | 1 - src/conf/virconftypes.h | 3 + src/driver-hypervisor.h | 20 + src/libvirt-domain-checkpoint.c | 7 +- src/libvirt-domain.c | 191 ++++ src/libvirt_private.syms | 13 +- src/libvirt_public.syms | 8 + src/libxl/libxl_driver.c | 2 +- src/qemu/Makefile.inc.am | 2 + src/qemu/qemu_backup.c | 895 ++++++++++++++++++ src/qemu/qemu_backup.h | 41 + src/qemu/qemu_block.c | 26 +- src/qemu/qemu_block.h | 5 +- src/qemu/qemu_blockjob.c | 171 ++-- src/qemu/qemu_blockjob.h | 19 + src/qemu/qemu_checkpoint.c | 232 +++-- src/qemu/qemu_checkpoint.h | 15 + src/qemu/qemu_domain.c | 161 +++- src/qemu/qemu_domain.h | 11 +- src/qemu/qemu_driver.c | 116 ++- src/qemu/qemu_hotplug.c | 2 +- src/qemu/qemu_migration.c | 3 +- src/qemu/qemu_monitor.c | 91 +- src/qemu/qemu_monitor.h | 49 +- src/qemu/qemu_monitor_json.c | 242 ++--- src/qemu/qemu_monitor_json.h | 33 +- src/remote/remote_driver.c | 3 + src/remote/remote_protocol.x | 53 +- src/remote_protocol-structs | 28 + src/test/test_driver.c | 2 +- src/util/vircgroup.c | 2 +- src/util/virhash.c | 85 +- src/util/virhash.h | 12 + tests/Makefile.am | 2 + tests/domainbackupxml2xmlin/backup-pull.xml | 9 + tests/domainbackupxml2xmlin/backup-push.xml | 9 + tests/domainbackupxml2xmlin/empty.xml | 1 + tests/domainbackupxml2xmlout/backup-pull.xml | 9 + tests/domainbackupxml2xmlout/backup-push.xml | 9 + tests/domainbackupxml2xmlout/empty.xml | 7 + tests/qemublocktest.c | 3 +- tests/qemumonitorjsontest.c | 53 +- .../qemustatusxml2xmldata/backup-pull-in.xml | 607 ++++++++++++ .../qemustatusxml2xmldata/backup-pull-out.xml | 1 + tests/qemuxml2xmltest.c | 2 + tests/virschematest.c | 2 + tools/Makefile.am | 1 + tools/virsh-backup.c | 209 ++++ tools/virsh-backup.h | 21 + tools/virsh-domain.c | 8 +- tools/virsh.c | 2 + tools/virsh.h | 1 + tools/virsh.pod | 37 + 71 files changed, 4133 insertions(+), 552 deletions(-) create mode 100644 docs/formatbackup.html.in create mode 100644 docs/schemas/domainbackup.rng create mode 100644 src/conf/backup_conf.c create mode 100644 src/conf/backup_conf.h create mode 100644 src/qemu/qemu_backup.c create mode 100644 src/qemu/qemu_backup.h create mode 100644 tests/domainbackupxml2xmlin/backup-pull.xml create mode 100644 tests/domainbackupxml2xmlin/backup-push.xml create mode 100644 tests/domainbackupxml2xmlin/empty.xml create mode 100644 tests/domainbackupxml2xmlout/backup-pull.xml create mode 100644 tests/domainbackupxml2xmlout/backup-push.xml create mode 100644 tests/domainbackupxml2xmlout/empty.xml create mode 100644 tests/qemustatusxml2xmldata/backup-pull-in.xml create mode 120000 tests/qemustatusxml2xmldata/backup-pull-out.xml create mode 100644 tools/virsh-backup.c create mode 100644 tools/virsh-backup.h -- 2.21.0

Introduce a new type virHashDataFreeSimple which has only a void * as argument for cases when knowing the name of the entry when freeing the hash entry is not required. Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/conf/domain_addr.c | 4 ++-- src/util/vircgroup.c | 2 +- src/util/virhash.c | 15 ++++++++++++++- src/util/virhash.h | 10 ++++++++++ 4 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/conf/domain_addr.c b/src/conf/domain_addr.c index d0026942aa..dd8e04576a 100644 --- a/src/conf/domain_addr.c +++ b/src/conf/domain_addr.c @@ -1037,14 +1037,14 @@ virDomainPCIAddressSetExtensionAlloc(virDomainPCIAddressSetPtr addrs, if (VIR_ALLOC(addrs->zpciIds) < 0) return -1; - if (!(addrs->zpciIds->uids = virHashCreateFull(10, NULL, + if (!(addrs->zpciIds->uids = virHashCreateFull(10, NULL, NULL, virZPCIAddrKeyCode, virZPCIAddrKeyEqual, virZPCIAddrKeyCopy, virZPCIAddrKeyFree))) goto error; - if (!(addrs->zpciIds->fids = virHashCreateFull(10, NULL, + if (!(addrs->zpciIds->fids = virHashCreateFull(10, NULL, NULL, virZPCIAddrKeyCode, virZPCIAddrKeyEqual, virZPCIAddrKeyCopy, diff --git a/src/util/vircgroup.c b/src/util/vircgroup.c index d824aee86d..21c0fe67e3 100644 --- a/src/util/vircgroup.c +++ b/src/util/vircgroup.c @@ -2600,7 +2600,7 @@ virCgroupKillRecursive(virCgroupPtr group, int signum) bool backendAvailable = false; virCgroupBackendPtr *backends = virCgroupBackendGetAll(); virHashTablePtr pids = virHashCreateFull(100, - NULL, + NULL, NULL, virCgroupPidCode, virCgroupPidEqual, virCgroupPidCopy, diff --git a/src/util/virhash.c b/src/util/virhash.c index a7fc620567..4d90fa5333 100644 --- a/src/util/virhash.c +++ b/src/util/virhash.c @@ -56,6 +56,7 @@ struct _virHashTable { size_t size; size_t nbElems; virHashDataFree dataFree; + virHashDataFreeSimple dataFreeSimple; virHashKeyCode keyCode; virHashKeyEqual keyEqual; virHashKeyCopy keyCopy; @@ -133,6 +134,7 @@ virHashComputeKey(const virHashTable *table, const void *name) */ virHashTablePtr virHashCreateFull(ssize_t size, virHashDataFree dataFree, + virHashDataFreeSimple dataFreeSimple, virHashKeyCode keyCode, virHashKeyEqual keyEqual, virHashKeyCopy keyCopy, @@ -149,7 +151,10 @@ virHashTablePtr virHashCreateFull(ssize_t size, table->seed = virRandomBits(32); table->size = size; table->nbElems = 0; - table->dataFree = dataFree; + if (dataFree) + table->dataFree = dataFree; + else + table->dataFreeSimple = dataFreeSimple; table->keyCode = keyCode; table->keyEqual = keyEqual; table->keyCopy = keyCopy; @@ -177,6 +182,7 @@ virHashTablePtr virHashCreate(ssize_t size, virHashDataFree dataFree) { return virHashCreateFull(size, dataFree, + NULL, virHashStrCode, virHashStrEqual, virHashStrCopy, @@ -298,6 +304,8 @@ virHashFree(virHashTablePtr table) if (table->dataFree) table->dataFree(iter->payload, iter->name); + if (table->dataFreeSimple) + table->dataFreeSimple(iter->payload); if (table->keyFree) table->keyFree(iter->name); VIR_FREE(iter); @@ -330,6 +338,8 @@ virHashAddOrUpdateEntry(virHashTablePtr table, const void *name, if (is_update) { if (table->dataFree) table->dataFree(entry->payload, entry->name); + if (table->dataFreeSimple) + table->dataFreeSimple(entry->payload); entry->payload = userdata; return 0; } else { @@ -456,9 +466,12 @@ void *virHashSteal(virHashTablePtr table, const void *name) void *data = virHashLookup(table, name); if (data) { virHashDataFree dataFree = table->dataFree; + virHashDataFreeSimple dataFreeSimple = table->dataFreeSimple; table->dataFree = NULL; + table->dataFreeSimple = NULL; virHashRemoveEntry(table, name); table->dataFree = dataFree; + table->dataFreeSimple = dataFreeSimple; } return data; } diff --git a/src/util/virhash.h b/src/util/virhash.h index b5e7c79260..94fe8e23e4 100644 --- a/src/util/virhash.h +++ b/src/util/virhash.h @@ -30,6 +30,15 @@ typedef virHashAtomic *virHashAtomicPtr; * Callback to free data from a hash. */ typedef void (*virHashDataFree) (void *payload, const void *name); +/** + * virHashDataFreeSimple: + * @payload: the data in the hash + * @name: the name associated + * + * Callback to free data from a hash. + */ +typedef void (*virHashDataFreeSimple) (void *payload); + /** * virHashIterator: * @payload: the data in the hash @@ -104,6 +113,7 @@ virHashAtomicPtr virHashAtomicNew(ssize_t size, virHashDataFree dataFree); virHashTablePtr virHashCreateFull(ssize_t size, virHashDataFree dataFree, + virHashDataFreeSimple dataFreeSimple, virHashKeyCode keyCode, virHashKeyEqual keyEqual, virHashKeyCopy keyCopy, -- 2.21.0

On 10/18/19 11:10 AM, Peter Krempa wrote:
Introduce a new type virHashDataFreeSimple which has only a void * as argument for cases when knowing the name of the entry when freeing the hash entry is not required.
Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/conf/domain_addr.c | 4 ++-- src/util/vircgroup.c | 2 +- src/util/virhash.c | 15 ++++++++++++++- src/util/virhash.h | 10 ++++++++++ 4 files changed, 27 insertions(+), 4 deletions(-)
This shows there were not many callers to virHashCreateFull. (Thankfully)
@@ -133,6 +134,7 @@ virHashComputeKey(const virHashTable *table, const void *name) */ virHashTablePtr virHashCreateFull(ssize_t size, virHashDataFree dataFree, + virHashDataFreeSimple dataFreeSimple,
Is there any way to create a union argument which takes either a dataFree or dataFreeSimple function, rather than having to have two separate parameters? But as there are not many callers, this does not hurt too much.
virHashKeyCode keyCode, virHashKeyEqual keyEqual, virHashKeyCopy keyCopy, @@ -149,7 +151,10 @@ virHashTablePtr virHashCreateFull(ssize_t size, table->seed = virRandomBits(32); table->size = size; table->nbElems = 0; - table->dataFree = dataFree; + if (dataFree) + table->dataFree = dataFree; + else + table->dataFreeSimple = dataFreeSimple;
I guess I'll need to see later in the series why we need this instead of being able to use virHashValueFree(). Are there really that many places where it is just too much boilerplate to add a simple one-liner forwarding function that passes the virHashDataFree signature with two parameters and calls the real freeing function with one parameter? Should this function fail if the user passes non-NULL pointers for both dataFree and dataFreeSimple, rather than blindly favoring only dataFree? But code-wise, the patch is correct. So if its use later in the series proves useful, then consider this as an ACK. -- Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3226 Virtualization: qemu.org | libvirt.org

On Fri, Oct 18, 2019 at 13:22:56 -0500, Eric Blake wrote:
On 10/18/19 11:10 AM, Peter Krempa wrote:
Introduce a new type virHashDataFreeSimple which has only a void * as argument for cases when knowing the name of the entry when freeing the hash entry is not required.
Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/conf/domain_addr.c | 4 ++-- src/util/vircgroup.c | 2 +- src/util/virhash.c | 15 ++++++++++++++- src/util/virhash.h | 10 ++++++++++ 4 files changed, 27 insertions(+), 4 deletions(-)
This shows there were not many callers to virHashCreateFull. (Thankfully)
@@ -133,6 +134,7 @@ virHashComputeKey(const virHashTable *table, const void *name) */ virHashTablePtr virHashCreateFull(ssize_t size, virHashDataFree dataFree, + virHashDataFreeSimple dataFreeSimple,
Is there any way to create a union argument which takes either a dataFree or dataFreeSimple function, rather than having to have two separate parameters? But as there are not many callers, this does not hurt too much.
I don't think there's a way that would result in simpler/cleaner code. An ugly but working way would be to just call the unionified pointer with two arguments as they share the first one. Otherwise it would require to store an boolean to switch which variant to use and that is basically equal to this implementation. I don't want to add any additional usage error code paths since the constructor reports only allocation errors and thus we will be able to always assume that it returns a valid pointer.
virHashKeyCode keyCode, virHashKeyEqual keyEqual, virHashKeyCopy keyCopy, @@ -149,7 +151,10 @@ virHashTablePtr virHashCreateFull(ssize_t size, table->seed = virRandomBits(32); table->size = size; table->nbElems = 0; - table->dataFree = dataFree; + if (dataFree) + table->dataFree = dataFree; + else + table->dataFreeSimple = dataFreeSimple;
I guess I'll need to see later in the series why we need this instead of being able to use virHashValueFree(). Are there really that many places where it is just too much boilerplate to add a simple one-liner forwarding function that passes the virHashDataFree signature with two parameters and calls the real freeing function with one parameter?
As said above I wanted to be correct code-wise. I could just typecast my freeing function to virHashDataFree and it would work.
Should this function fail if the user passes non-NULL pointers for both dataFree and dataFreeSimple, rather than blindly favoring only dataFree?
I think it is ugly.
But code-wise, the patch is correct. So if its use later in the series proves useful, then consider this as an ACK.
-- Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3226 Virtualization: qemu.org | libvirt.org
-- libvir-list mailing list libvir-list@redhat.com https://www.redhat.com/mailman/listinfo/libvir-list

Add a simpler constructor for hash tables which specifically does not require specifying the initial hash size and uses simpler freeing function. The initial hash table size usually is not important as the hash table is growing when it reaches certain number of entries in one bucket. Additionally many callers pass in a random small number for ad-hoc table use so using a central one will simplify things. Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/libvirt_private.syms | 1 + src/util/virhash.c | 21 +++++++++++++++++++++ src/util/virhash.h | 1 + 3 files changed, 23 insertions(+) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 0da02bb8bd..c155f51174 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -2066,6 +2066,7 @@ virHashForEach; virHashFree; virHashGetItems; virHashLookup; +virHashNew; virHashRemoveAll; virHashRemoveEntry; virHashRemoveSet; diff --git a/src/util/virhash.c b/src/util/virhash.c index 4d90fa5333..de03032bf1 100644 --- a/src/util/virhash.c +++ b/src/util/virhash.c @@ -169,6 +169,27 @@ virHashTablePtr virHashCreateFull(ssize_t size, } +/** + * virHashNew: + * @dataFree: callback to free data + * + * Create a new virHashTablePtr. + * + * Returns the newly created object, or NULL if an error occurred. + */ +virHashTablePtr +virHashNew(virHashDataFreeSimple dataFree) +{ + return virHashCreateFull(32, + NULL, + dataFree, + virHashStrCode, + virHashStrEqual, + virHashStrCopy, + virHashStrFree); +} + + /** * virHashCreate: * @size: the size of the hash table diff --git a/src/util/virhash.h b/src/util/virhash.h index 94fe8e23e4..d7de0618cb 100644 --- a/src/util/virhash.h +++ b/src/util/virhash.h @@ -107,6 +107,7 @@ typedef void (*virHashKeyFree)(void *name); /* * Constructor and destructor. */ +virHashTablePtr virHashNew(virHashDataFreeSimple dataFree); virHashTablePtr virHashCreate(ssize_t size, virHashDataFree dataFree); virHashAtomicPtr virHashAtomicNew(ssize_t size, -- 2.21.0

On 10/18/19 11:10 AM, Peter Krempa wrote:
Add a simpler constructor for hash tables which specifically does not require specifying the initial hash size and uses simpler freeing function.
The initial hash table size usually is not important as the hash table is growing when it reaches certain number of entries in one bucket. Additionally many callers pass in a random small number for ad-hoc table use so using a central one will simplify things.
Signed-off-by: Peter Krempa <pkrempa@redhat.com> ---
Again, seeing where this is used will be helpful, but as written, the addition is correct, so ACK -- Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3226 Virtualization: qemu.org | libvirt.org

Add a helper that checks whether an entry with given name exists but does not touch the userdata. Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/libvirt_private.syms | 1 + src/util/virhash.c | 49 ++++++++++++++++++++++++++++++++-------- src/util/virhash.h | 1 + 3 files changed, 42 insertions(+), 9 deletions(-) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index c155f51174..588f0a4356 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -2065,6 +2065,7 @@ virHashEqual; virHashForEach; virHashFree; virHashGetItems; +virHashHasEntry; virHashLookup; virHashNew; virHashRemoveAll; diff --git a/src/util/virhash.c b/src/util/virhash.c index de03032bf1..df07e37e42 100644 --- a/src/util/virhash.c +++ b/src/util/virhash.c @@ -445,6 +445,26 @@ virHashAtomicUpdate(virHashAtomicPtr table, } +static virHashEntryPtr +virHashGetEntry(const virHashTable *table, + const void *name) +{ + size_t key; + virHashEntryPtr entry; + + if (!table || !name) + return NULL; + + key = virHashComputeKey(table, name); + for (entry = table->table[key]; entry; entry = entry->next) { + if (table->keyEqual(entry->name, name)) + return entry; + } + + return NULL; +} + + /** * virHashLookup: * @table: the hash table @@ -457,18 +477,29 @@ virHashAtomicUpdate(virHashAtomicPtr table, void * virHashLookup(const virHashTable *table, const void *name) { - size_t key; - virHashEntryPtr entry; + virHashEntryPtr entry = virHashGetEntry(table, name); - if (!table || !name) + if (!entry) return NULL; - key = virHashComputeKey(table, name); - for (entry = table->table[key]; entry; entry = entry->next) { - if (table->keyEqual(entry->name, name)) - return entry->payload; - } - return NULL; + return entry->payload; +} + + +/** + * virHashHasEntry: + * @table: the hash table + * @name: the name of the userdata + * + * Find whether entry specified by @name exists. + * + * Returns true if the entry exists and false otherwise + */ +bool +virHashHasEntry(const virHashTable *table, + const void *name) +{ + return !!virHashGetEntry(table, name); } diff --git a/src/util/virhash.h b/src/util/virhash.h index d7de0618cb..8087965ee9 100644 --- a/src/util/virhash.h +++ b/src/util/virhash.h @@ -150,6 +150,7 @@ ssize_t virHashRemoveAll(virHashTablePtr table); * Retrieve the userdata. */ void *virHashLookup(const virHashTable *table, const void *name); +bool virHashHasEntry(const virHashTable *table, const void *name); /* * Retrieve & remove the userdata. -- 2.21.0

On 10/18/19 11:10 AM, Peter Krempa wrote:
Add a helper that checks whether an entry with given name exists but does not touch the userdata.
Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- ACK
-- Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3226 Virtualization: qemu.org | libvirt.org

qemuDomainPrepareDiskSourceData historically prepared everything but we'we split out the majority of the functionality so that it sets up predominantely only according to the configuration of the disk. There was one leftover bit of setting the gluster debug level from the config. Split this out into a separate function so that qemuDomainPrepareDiskSourceData only perpares based on the disk. Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_domain.c | 51 +++++++++++++++++++++++++++++------------- src/qemu/qemu_domain.h | 4 +--- tests/qemublocktest.c | 2 +- 3 files changed, 38 insertions(+), 19 deletions(-) diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index a97bf65e7f..6a93dd5293 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -10251,6 +10251,32 @@ qemuDomainStorageSourceValidateDepth(virStorageSourcePtr src, } +/** + * qemuDomainPrepareStorageSourceConfig: + * @src: storage source to configure + * @cfg: qemu driver config object + * @qemuCaps: capabilities of qemu + * + * Set properties of @src based on the qemu driver config @cfg. + * + */ +static void +qemuDomainPrepareStorageSourceConfig(virStorageSourcePtr src, + virQEMUDriverConfigPtr cfg, + virQEMUCapsPtr qemuCaps) +{ + if (!cfg) + return; + + if (src->type == VIR_STORAGE_TYPE_NETWORK && + src->protocol == VIR_STORAGE_NET_PROTOCOL_GLUSTER && + virQEMUCapsGet(qemuCaps, QEMU_CAPS_GLUSTER_DEBUG_LEVEL)) { + src->debug = true; + src->debugLevel = cfg->glusterDebugLevel; + } +} + + /** * qemuDomainDetermineDiskChain: * @driver: qemu driver object @@ -10356,7 +10382,9 @@ qemuDomainDetermineDiskChain(virQEMUDriverPtr driver, if (qemuDomainValidateStorageSource(n, priv->qemuCaps) < 0) return -1; - if (qemuDomainPrepareDiskSourceData(disk, n, cfg, priv->qemuCaps) < 0) + qemuDomainPrepareStorageSourceConfig(n, cfg, priv->qemuCaps); + + if (qemuDomainPrepareDiskSourceData(disk, n) < 0) return -1; if (virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BLOCKDEV) && @@ -15060,7 +15088,6 @@ qemuDomainCheckCCWS390AddressSupport(const virDomainDef *def, * * @disk: Disk config object * @src: source to start from - * @cfg: qemu driver config object * * Prepares various aspects of a storage source belonging to a disk backing * chain. This function should be also called for detected backing chain @@ -15068,22 +15095,12 @@ qemuDomainCheckCCWS390AddressSupport(const virDomainDef *def, */ int qemuDomainPrepareDiskSourceData(virDomainDiskDefPtr disk, - virStorageSourcePtr src, - virQEMUDriverConfigPtr cfg, - virQEMUCapsPtr qemuCaps) + virStorageSourcePtr src) { /* transfer properties valid only for the top level image */ if (src == disk->src) src->detect_zeroes = disk->detect_zeroes; - if (cfg && - src->type == VIR_STORAGE_TYPE_NETWORK && - src->protocol == VIR_STORAGE_NET_PROTOCOL_GLUSTER && - virQEMUCapsGet(qemuCaps, QEMU_CAPS_GLUSTER_DEBUG_LEVEL)) { - src->debug = true; - src->debugLevel = cfg->glusterDebugLevel; - } - /* transfer properties valid for the full chain */ src->iomode = disk->iomode; src->cachemode = disk->cachemode; @@ -15144,7 +15161,9 @@ qemuDomainPrepareDiskSourceLegacy(virDomainDiskDefPtr disk, if (qemuDomainValidateStorageSource(disk->src, priv->qemuCaps) < 0) return -1; - if (qemuDomainPrepareDiskSourceData(disk, disk->src, cfg, priv->qemuCaps) < 0) + qemuDomainPrepareStorageSourceConfig(disk->src, cfg, priv->qemuCaps); + + if (qemuDomainPrepareDiskSourceData(disk, disk->src) < 0) return -1; if (qemuDomainSecretStorageSourcePrepare(priv, disk->src, @@ -15178,7 +15197,9 @@ qemuDomainPrepareStorageSourceBlockdev(virDomainDiskDefPtr disk, if (qemuDomainValidateStorageSource(src, priv->qemuCaps) < 0) return -1; - if (qemuDomainPrepareDiskSourceData(disk, src, cfg, priv->qemuCaps) < 0) + qemuDomainPrepareStorageSourceConfig(src, cfg, priv->qemuCaps); + + if (qemuDomainPrepareDiskSourceData(disk, src) < 0) return -1; if (qemuDomainSecretStorageSourcePrepare(priv, src, diff --git a/src/qemu/qemu_domain.h b/src/qemu/qemu_domain.h index d703417862..14367f7320 100644 --- a/src/qemu/qemu_domain.h +++ b/src/qemu/qemu_domain.h @@ -1169,9 +1169,7 @@ qemuDomainCheckCCWS390AddressSupport(const virDomainDef *def, int qemuDomainPrepareDiskSourceData(virDomainDiskDefPtr disk, - virStorageSourcePtr src, - virQEMUDriverConfigPtr cfg, - virQEMUCapsPtr qemuCaps) + virStorageSourcePtr src) G_GNUC_WARN_UNUSED_RESULT; diff --git a/tests/qemublocktest.c b/tests/qemublocktest.c index 0f073682ca..9b7abceb40 100644 --- a/tests/qemublocktest.c +++ b/tests/qemublocktest.c @@ -223,7 +223,7 @@ testQemuDiskXMLToProps(const void *opaque) if (qemuDomainValidateStorageSource(n, data->qemuCaps) < 0) goto cleanup; - if (qemuDomainPrepareDiskSourceData(disk, n, NULL, data->qemuCaps) < 0) + if (qemuDomainPrepareDiskSourceData(disk, n) < 0) goto cleanup; if (!(formatProps = qemuBlockStorageSourceGetBlockdevProps(n, n->backingStore)) || -- 2.21.0

On 10/18/19 1:10 PM, Peter Krempa wrote:
qemuDomainPrepareDiskSourceData historically prepared everything but we'we split out the majority of the functionality so that it sets up predominantely only according to the configuration of the disk. There was one leftover bit of setting the gluster debug level from the config.
Split this out into a separate function so that qemuDomainPrepareDiskSourceData only perpares based on the disk.
s/perpares/prepares Reviewed-by: Daniel Henrique Barboza <danielhb413@gmail.com>
Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_domain.c | 51 +++++++++++++++++++++++++++++------------- src/qemu/qemu_domain.h | 4 +--- tests/qemublocktest.c | 2 +- 3 files changed, 38 insertions(+), 19 deletions(-)
diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index a97bf65e7f..6a93dd5293 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -10251,6 +10251,32 @@ qemuDomainStorageSourceValidateDepth(virStorageSourcePtr src, }
+/** + * qemuDomainPrepareStorageSourceConfig: + * @src: storage source to configure + * @cfg: qemu driver config object + * @qemuCaps: capabilities of qemu + * + * Set properties of @src based on the qemu driver config @cfg. + * + */ +static void +qemuDomainPrepareStorageSourceConfig(virStorageSourcePtr src, + virQEMUDriverConfigPtr cfg, + virQEMUCapsPtr qemuCaps) +{ + if (!cfg) + return; + + if (src->type == VIR_STORAGE_TYPE_NETWORK && + src->protocol == VIR_STORAGE_NET_PROTOCOL_GLUSTER && + virQEMUCapsGet(qemuCaps, QEMU_CAPS_GLUSTER_DEBUG_LEVEL)) { + src->debug = true; + src->debugLevel = cfg->glusterDebugLevel; + } +} + + /** * qemuDomainDetermineDiskChain: * @driver: qemu driver object @@ -10356,7 +10382,9 @@ qemuDomainDetermineDiskChain(virQEMUDriverPtr driver, if (qemuDomainValidateStorageSource(n, priv->qemuCaps) < 0) return -1;
- if (qemuDomainPrepareDiskSourceData(disk, n, cfg, priv->qemuCaps) < 0) + qemuDomainPrepareStorageSourceConfig(n, cfg, priv->qemuCaps); + + if (qemuDomainPrepareDiskSourceData(disk, n) < 0) return -1;
if (virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BLOCKDEV) && @@ -15060,7 +15088,6 @@ qemuDomainCheckCCWS390AddressSupport(const virDomainDef *def, * * @disk: Disk config object * @src: source to start from - * @cfg: qemu driver config object * * Prepares various aspects of a storage source belonging to a disk backing * chain. This function should be also called for detected backing chain @@ -15068,22 +15095,12 @@ qemuDomainCheckCCWS390AddressSupport(const virDomainDef *def, */ int qemuDomainPrepareDiskSourceData(virDomainDiskDefPtr disk, - virStorageSourcePtr src, - virQEMUDriverConfigPtr cfg, - virQEMUCapsPtr qemuCaps) + virStorageSourcePtr src) { /* transfer properties valid only for the top level image */ if (src == disk->src) src->detect_zeroes = disk->detect_zeroes;
- if (cfg && - src->type == VIR_STORAGE_TYPE_NETWORK && - src->protocol == VIR_STORAGE_NET_PROTOCOL_GLUSTER && - virQEMUCapsGet(qemuCaps, QEMU_CAPS_GLUSTER_DEBUG_LEVEL)) { - src->debug = true; - src->debugLevel = cfg->glusterDebugLevel; - } - /* transfer properties valid for the full chain */ src->iomode = disk->iomode; src->cachemode = disk->cachemode; @@ -15144,7 +15161,9 @@ qemuDomainPrepareDiskSourceLegacy(virDomainDiskDefPtr disk, if (qemuDomainValidateStorageSource(disk->src, priv->qemuCaps) < 0) return -1;
- if (qemuDomainPrepareDiskSourceData(disk, disk->src, cfg, priv->qemuCaps) < 0) + qemuDomainPrepareStorageSourceConfig(disk->src, cfg, priv->qemuCaps); + + if (qemuDomainPrepareDiskSourceData(disk, disk->src) < 0) return -1;
if (qemuDomainSecretStorageSourcePrepare(priv, disk->src, @@ -15178,7 +15197,9 @@ qemuDomainPrepareStorageSourceBlockdev(virDomainDiskDefPtr disk, if (qemuDomainValidateStorageSource(src, priv->qemuCaps) < 0) return -1;
- if (qemuDomainPrepareDiskSourceData(disk, src, cfg, priv->qemuCaps) < 0) + qemuDomainPrepareStorageSourceConfig(src, cfg, priv->qemuCaps); + + if (qemuDomainPrepareDiskSourceData(disk, src) < 0) return -1;
if (qemuDomainSecretStorageSourcePrepare(priv, src, diff --git a/src/qemu/qemu_domain.h b/src/qemu/qemu_domain.h index d703417862..14367f7320 100644 --- a/src/qemu/qemu_domain.h +++ b/src/qemu/qemu_domain.h @@ -1169,9 +1169,7 @@ qemuDomainCheckCCWS390AddressSupport(const virDomainDef *def,
int qemuDomainPrepareDiskSourceData(virDomainDiskDefPtr disk, - virStorageSourcePtr src, - virQEMUDriverConfigPtr cfg, - virQEMUCapsPtr qemuCaps) + virStorageSourcePtr src) G_GNUC_WARN_UNUSED_RESULT;
diff --git a/tests/qemublocktest.c b/tests/qemublocktest.c index 0f073682ca..9b7abceb40 100644 --- a/tests/qemublocktest.c +++ b/tests/qemublocktest.c @@ -223,7 +223,7 @@ testQemuDiskXMLToProps(const void *opaque) if (qemuDomainValidateStorageSource(n, data->qemuCaps) < 0) goto cleanup;
- if (qemuDomainPrepareDiskSourceData(disk, n, NULL, data->qemuCaps) < 0) + if (qemuDomainPrepareDiskSourceData(disk, n) < 0) goto cleanup;
if (!(formatProps = qemuBlockStorageSourceGetBlockdevProps(n, n->backingStore)) ||

On 10/18/19 11:10 AM, Peter Krempa wrote:
qemuDomainPrepareDiskSourceData historically prepared everything but we'we split out the majority of the functionality so that it sets up
we've
predominantely only according to the configuration of the disk. There
predominately
was one leftover bit of setting the gluster debug level from the config.
Split this out into a separate function so that qemuDomainPrepareDiskSourceData only perpares based on the disk.
prepares
Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_domain.c | 51 +++++++++++++++++++++++++++++------------- src/qemu/qemu_domain.h | 4 +--- tests/qemublocktest.c | 2 +- 3 files changed, 38 insertions(+), 19 deletions(-)
ACK -- Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3226 Virtualization: qemu.org | libvirt.org

The function does not do anything that could fail. Remove the return value. Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_domain.c | 16 ++++------------ src/qemu/qemu_domain.h | 5 ++--- tests/qemublocktest.c | 3 +-- 3 files changed, 7 insertions(+), 17 deletions(-) diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index 6a93dd5293..29212eaa10 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -10383,9 +10383,7 @@ qemuDomainDetermineDiskChain(virQEMUDriverPtr driver, return -1; qemuDomainPrepareStorageSourceConfig(n, cfg, priv->qemuCaps); - - if (qemuDomainPrepareDiskSourceData(disk, n) < 0) - return -1; + qemuDomainPrepareDiskSourceData(disk, n); if (virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BLOCKDEV) && qemuDomainPrepareStorageSourceBlockdev(disk, n, priv, cfg) < 0) @@ -15093,7 +15091,7 @@ qemuDomainCheckCCWS390AddressSupport(const virDomainDef *def, * chain. This function should be also called for detected backing chain * members. */ -int +void qemuDomainPrepareDiskSourceData(virDomainDiskDefPtr disk, virStorageSourcePtr src) { @@ -15108,8 +15106,6 @@ qemuDomainPrepareDiskSourceData(virDomainDiskDefPtr disk, if (disk->device == VIR_DOMAIN_DISK_DEVICE_FLOPPY) src->floppyimg = true; - - return 0; } @@ -15162,9 +15158,7 @@ qemuDomainPrepareDiskSourceLegacy(virDomainDiskDefPtr disk, return -1; qemuDomainPrepareStorageSourceConfig(disk->src, cfg, priv->qemuCaps); - - if (qemuDomainPrepareDiskSourceData(disk, disk->src) < 0) - return -1; + qemuDomainPrepareDiskSourceData(disk, disk->src); if (qemuDomainSecretStorageSourcePrepare(priv, disk->src, disk->info.alias, @@ -15198,9 +15192,7 @@ qemuDomainPrepareStorageSourceBlockdev(virDomainDiskDefPtr disk, return -1; qemuDomainPrepareStorageSourceConfig(src, cfg, priv->qemuCaps); - - if (qemuDomainPrepareDiskSourceData(disk, src) < 0) - return -1; + qemuDomainPrepareDiskSourceData(disk, src); if (qemuDomainSecretStorageSourcePrepare(priv, src, src->nodestorage, diff --git a/src/qemu/qemu_domain.h b/src/qemu/qemu_domain.h index 14367f7320..5b3d84cea7 100644 --- a/src/qemu/qemu_domain.h +++ b/src/qemu/qemu_domain.h @@ -1167,10 +1167,9 @@ qemuDomainCheckCCWS390AddressSupport(const virDomainDef *def, virQEMUCapsPtr qemuCaps, const char *devicename); -int +void qemuDomainPrepareDiskSourceData(virDomainDiskDefPtr disk, - virStorageSourcePtr src) - G_GNUC_WARN_UNUSED_RESULT; + virStorageSourcePtr src); int diff --git a/tests/qemublocktest.c b/tests/qemublocktest.c index 9b7abceb40..08eec11ef9 100644 --- a/tests/qemublocktest.c +++ b/tests/qemublocktest.c @@ -223,8 +223,7 @@ testQemuDiskXMLToProps(const void *opaque) if (qemuDomainValidateStorageSource(n, data->qemuCaps) < 0) goto cleanup; - if (qemuDomainPrepareDiskSourceData(disk, n) < 0) - goto cleanup; + qemuDomainPrepareDiskSourceData(disk, n); if (!(formatProps = qemuBlockStorageSourceGetBlockdevProps(n, n->backingStore)) || !(storageSrcOnlyProps = qemuBlockStorageSourceGetBackendProps(n, false, true, true)) || -- 2.21.0

On 10/18/19 1:10 PM, Peter Krempa wrote:
The function does not do anything that could fail. Remove the return value.
Signed-off-by: Peter Krempa <pkrempa@redhat.com> ---
Reviewed-by: Daniel Henrique Barboza <danielhb413@gmail.com>
src/qemu/qemu_domain.c | 16 ++++------------ src/qemu/qemu_domain.h | 5 ++--- tests/qemublocktest.c | 3 +-- 3 files changed, 7 insertions(+), 17 deletions(-)
diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index 6a93dd5293..29212eaa10 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -10383,9 +10383,7 @@ qemuDomainDetermineDiskChain(virQEMUDriverPtr driver, return -1;
qemuDomainPrepareStorageSourceConfig(n, cfg, priv->qemuCaps); - - if (qemuDomainPrepareDiskSourceData(disk, n) < 0) - return -1; + qemuDomainPrepareDiskSourceData(disk, n);
if (virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BLOCKDEV) && qemuDomainPrepareStorageSourceBlockdev(disk, n, priv, cfg) < 0) @@ -15093,7 +15091,7 @@ qemuDomainCheckCCWS390AddressSupport(const virDomainDef *def, * chain. This function should be also called for detected backing chain * members. */ -int +void qemuDomainPrepareDiskSourceData(virDomainDiskDefPtr disk, virStorageSourcePtr src) { @@ -15108,8 +15106,6 @@ qemuDomainPrepareDiskSourceData(virDomainDiskDefPtr disk,
if (disk->device == VIR_DOMAIN_DISK_DEVICE_FLOPPY) src->floppyimg = true; - - return 0; }
@@ -15162,9 +15158,7 @@ qemuDomainPrepareDiskSourceLegacy(virDomainDiskDefPtr disk, return -1;
qemuDomainPrepareStorageSourceConfig(disk->src, cfg, priv->qemuCaps); - - if (qemuDomainPrepareDiskSourceData(disk, disk->src) < 0) - return -1; + qemuDomainPrepareDiskSourceData(disk, disk->src);
if (qemuDomainSecretStorageSourcePrepare(priv, disk->src, disk->info.alias, @@ -15198,9 +15192,7 @@ qemuDomainPrepareStorageSourceBlockdev(virDomainDiskDefPtr disk, return -1;
qemuDomainPrepareStorageSourceConfig(src, cfg, priv->qemuCaps); - - if (qemuDomainPrepareDiskSourceData(disk, src) < 0) - return -1; + qemuDomainPrepareDiskSourceData(disk, src);
if (qemuDomainSecretStorageSourcePrepare(priv, src, src->nodestorage, diff --git a/src/qemu/qemu_domain.h b/src/qemu/qemu_domain.h index 14367f7320..5b3d84cea7 100644 --- a/src/qemu/qemu_domain.h +++ b/src/qemu/qemu_domain.h @@ -1167,10 +1167,9 @@ qemuDomainCheckCCWS390AddressSupport(const virDomainDef *def, virQEMUCapsPtr qemuCaps, const char *devicename);
-int +void qemuDomainPrepareDiskSourceData(virDomainDiskDefPtr disk, - virStorageSourcePtr src) - G_GNUC_WARN_UNUSED_RESULT; + virStorageSourcePtr src);
int diff --git a/tests/qemublocktest.c b/tests/qemublocktest.c index 9b7abceb40..08eec11ef9 100644 --- a/tests/qemublocktest.c +++ b/tests/qemublocktest.c @@ -223,8 +223,7 @@ testQemuDiskXMLToProps(const void *opaque) if (qemuDomainValidateStorageSource(n, data->qemuCaps) < 0) goto cleanup;
- if (qemuDomainPrepareDiskSourceData(disk, n) < 0) - goto cleanup; + qemuDomainPrepareDiskSourceData(disk, n);
if (!(formatProps = qemuBlockStorageSourceGetBlockdevProps(n, n->backingStore)) || !(storageSrcOnlyProps = qemuBlockStorageSourceGetBackendProps(n, false, true, true)) ||

Note in the comment that this function prepares the storage source based on the configuration of the disk. Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_domain.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index 29212eaa10..259cf51e2b 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -15088,8 +15088,8 @@ qemuDomainCheckCCWS390AddressSupport(const virDomainDef *def, * @src: source to start from * * Prepares various aspects of a storage source belonging to a disk backing - * chain. This function should be also called for detected backing chain - * members. + * chain based on the disk configuration. This function should be also called + * for detected backing chain members. */ void qemuDomainPrepareDiskSourceData(virDomainDiskDefPtr disk, -- 2.21.0

On 10/18/19 1:10 PM, Peter Krempa wrote:
Note in the comment that this function prepares the storage source based on the configuration of the disk.
Signed-off-by: Peter Krempa <pkrempa@redhat.com> ---
Reviewed-by: Daniel Henrique Barboza <danielhb413@gmail.com>
src/qemu/qemu_domain.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index 29212eaa10..259cf51e2b 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -15088,8 +15088,8 @@ qemuDomainCheckCCWS390AddressSupport(const virDomainDef *def, * @src: source to start from * * Prepares various aspects of a storage source belonging to a disk backing - * chain. This function should be also called for detected backing chain - * members. + * chain based on the disk configuration. This function should be also called + * for detected backing chain members. */ void qemuDomainPrepareDiskSourceData(virDomainDiskDefPtr disk,

In some cases we want to prepare a @src which is not meant to belong to a disk and thus does not require us to copy the data. Allow passing in NULL @disk into qemuDomainPrepareDiskSourceData. Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_domain.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index 259cf51e2b..3deb69cb63 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -15095,6 +15095,9 @@ void qemuDomainPrepareDiskSourceData(virDomainDiskDefPtr disk, virStorageSourcePtr src) { + if (!disk) + return; + /* transfer properties valid only for the top level image */ if (src == disk->src) src->detect_zeroes = disk->detect_zeroes; -- 2.21.0

On 10/18/19 1:10 PM, Peter Krempa wrote:
In some cases we want to prepare a @src which is not meant to belong to a disk and thus does not require us to copy the data. Allow passing in NULL @disk into qemuDomainPrepareDiskSourceData.
Signed-off-by: Peter Krempa <pkrempa@redhat.com> ---
This is worth pushing as a bug fix IMO. Of all callers of qemuDomainPrepareDiskSourceData, just qemuDomainPrepareDiskSourceLegacy ensures that disk isn't NULL (and not explicitly, more like a side effect of qemuDomainValidateStorageSource being used with disk->src there). In all other places there is no guard of disk == NULL. Or perhaps disk=NULL never happens in the current usage of this function and I'm being pendant. Anyway .... Reviewed-by: Daniel Henrique Barboza <danielhb413@gmail.com>
src/qemu/qemu_domain.c | 3 +++ 1 file changed, 3 insertions(+)
diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index 259cf51e2b..3deb69cb63 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -15095,6 +15095,9 @@ void qemuDomainPrepareDiskSourceData(virDomainDiskDefPtr disk, virStorageSourcePtr src) { + if (!disk) + return; + /* transfer properties valid only for the top level image */ if (src == disk->src) src->detect_zeroes = disk->detect_zeroes;

On Fri, Oct 18, 2019 at 16:08:13 -0300, Daniel Henrique Barboza wrote:
On 10/18/19 1:10 PM, Peter Krempa wrote:
In some cases we want to prepare a @src which is not meant to belong to a disk and thus does not require us to copy the data. Allow passing in NULL @disk into qemuDomainPrepareDiskSourceData.
Signed-off-by: Peter Krempa <pkrempa@redhat.com> ---
This is worth pushing as a bug fix IMO. Of all callers of qemuDomainPrepareDiskSourceData, just qemuDomainPrepareDiskSourceLegacy ensures that disk isn't NULL (and not explicitly, more like a side effect of qemuDomainValidateStorageSource being used with disk->src there). In all other places there is no guard of disk == NULL.
Or perhaps disk=NULL never happens in the current usage of this function and I'm being pendant. Anyway ....
It is not a bug fix. Out of the 3 non-test callers, two dereference disk anyways in other places thus this fix will not help. In fact it is meant to make the third caller qemuDomainPrepareStorageSourceBlockdev callable with NULL disk which is the intended purpose of this patch. That helper is currently used only in cases where qemuDomainPrepareDiskSourceLegacy would be used and thus disk must be non-NULL. The use with NULL disk will appear later in this series.

Last use was removed in 29682196d8f. Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/conf/domain_conf.c | 11 ----------- src/conf/domain_conf.h | 1 - src/libvirt_private.syms | 1 - 3 files changed, 13 deletions(-) diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index 88e93f6fb8..8b4e80fa8d 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -16922,17 +16922,6 @@ virDomainDiskIndexByName(virDomainDefPtr def, const char *name, return candidate; } -/* Return the path to a disk image if a string identifies at least one - * disk belonging to the domain (both device strings 'vda' and paths - * '/path/to/file' are converted into '/path/to/file'). */ -const char * -virDomainDiskPathByName(virDomainDefPtr def, const char *name) -{ - int idx = virDomainDiskIndexByName(def, name, true); - - return idx < 0 ? NULL : virDomainDiskGetSource(def->disks[idx]); -} - virDomainDiskDefPtr virDomainDiskByName(virDomainDefPtr def, const char *name, diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index b33e5334f4..ad8e88c2ee 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -3162,7 +3162,6 @@ int virDomainDiskIndexByName(virDomainDefPtr def, const char *name, virDomainDiskDefPtr virDomainDiskByName(virDomainDefPtr def, const char *name, bool allow_ambiguous); -const char *virDomainDiskPathByName(virDomainDefPtr, const char *name); int virDomainDiskInsert(virDomainDefPtr def, virDomainDiskDefPtr disk) G_GNUC_WARN_UNUSED_RESULT; diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 588f0a4356..e6d40d1cbe 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -362,7 +362,6 @@ virDomainDiskMirrorStateTypeFromString; virDomainDiskMirrorStateTypeToString; virDomainDiskModelTypeFromString; virDomainDiskModelTypeToString; -virDomainDiskPathByName; virDomainDiskRemove; virDomainDiskRemoveByName; virDomainDiskSetBlockIOTune; -- 2.21.0

On 10/18/19 1:10 PM, Peter Krempa wrote:
Last use was removed in 29682196d8f.
Signed-off-by: Peter Krempa <pkrempa@redhat.com> ---
Reviewed-by: Daniel Henrique Barboza <danielhb413@gmail.com>
src/conf/domain_conf.c | 11 ----------- src/conf/domain_conf.h | 1 - src/libvirt_private.syms | 1 - 3 files changed, 13 deletions(-)
diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index 88e93f6fb8..8b4e80fa8d 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -16922,17 +16922,6 @@ virDomainDiskIndexByName(virDomainDefPtr def, const char *name, return candidate; }
-/* Return the path to a disk image if a string identifies at least one - * disk belonging to the domain (both device strings 'vda' and paths - * '/path/to/file' are converted into '/path/to/file'). */ -const char * -virDomainDiskPathByName(virDomainDefPtr def, const char *name) -{ - int idx = virDomainDiskIndexByName(def, name, true); - - return idx < 0 ? NULL : virDomainDiskGetSource(def->disks[idx]); -} - virDomainDiskDefPtr virDomainDiskByName(virDomainDefPtr def, const char *name, diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index b33e5334f4..ad8e88c2ee 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -3162,7 +3162,6 @@ int virDomainDiskIndexByName(virDomainDefPtr def, const char *name, virDomainDiskDefPtr virDomainDiskByName(virDomainDefPtr def, const char *name, bool allow_ambiguous); -const char *virDomainDiskPathByName(virDomainDefPtr, const char *name); int virDomainDiskInsert(virDomainDefPtr def, virDomainDiskDefPtr disk) G_GNUC_WARN_UNUSED_RESULT; diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 588f0a4356..e6d40d1cbe 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -362,7 +362,6 @@ virDomainDiskMirrorStateTypeFromString; virDomainDiskMirrorStateTypeToString; virDomainDiskModelTypeFromString; virDomainDiskModelTypeToString; -virDomainDiskPathByName; virDomainDiskRemove; virDomainDiskRemoveByName; virDomainDiskSetBlockIOTune;

Introduce a simpler replacement for virDomainDiskByName when looking up by disk target. Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/conf/domain_conf.c | 16 ++++++++++++++++ src/conf/domain_conf.h | 4 ++++ src/libvirt_private.syms | 1 + 3 files changed, 21 insertions(+) diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index 8b4e80fa8d..1fffd56a22 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -16931,6 +16931,22 @@ virDomainDiskByName(virDomainDefPtr def, return idx < 0 ? NULL : def->disks[idx]; } + +virDomainDiskDefPtr +virDomainDiskByTarget(virDomainDefPtr def, + const char *dst) +{ + size_t i; + + for (i = 0; i < def->ndisks; i++) { + if (STREQ(def->disks[i]->dst, dst)) + return def->disks[i]; + } + + return NULL; +} + + int virDomainDiskInsert(virDomainDefPtr def, virDomainDiskDefPtr disk) { diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index ad8e88c2ee..690aa3c84b 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -3162,6 +3162,10 @@ int virDomainDiskIndexByName(virDomainDefPtr def, const char *name, virDomainDiskDefPtr virDomainDiskByName(virDomainDefPtr def, const char *name, bool allow_ambiguous); +virDomainDiskDefPtr +virDomainDiskByTarget(virDomainDefPtr def, + const char *dst); + int virDomainDiskInsert(virDomainDefPtr def, virDomainDiskDefPtr disk) G_GNUC_WARN_UNUSED_RESULT; diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index e6d40d1cbe..2379c1ed8b 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -330,6 +330,7 @@ virDomainDiskBackingStoreParse; virDomainDiskBusTypeToString; virDomainDiskByAddress; virDomainDiskByName; +virDomainDiskByTarget; virDomainDiskCacheTypeFromString; virDomainDiskCacheTypeToString; virDomainDiskDefAssignAddress; -- 2.21.0

On 10/18/19 1:10 PM, Peter Krempa wrote:
Introduce a simpler replacement for virDomainDiskByName when looking up by disk target.
Signed-off-by: Peter Krempa <pkrempa@redhat.com> ---
Reviewed-by: Daniel Henrique Barboza <danielhb413@gmail.com>
src/conf/domain_conf.c | 16 ++++++++++++++++ src/conf/domain_conf.h | 4 ++++ src/libvirt_private.syms | 1 + 3 files changed, 21 insertions(+)
diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index 8b4e80fa8d..1fffd56a22 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -16931,6 +16931,22 @@ virDomainDiskByName(virDomainDefPtr def, return idx < 0 ? NULL : def->disks[idx]; }
+ +virDomainDiskDefPtr +virDomainDiskByTarget(virDomainDefPtr def, + const char *dst) +{ + size_t i; + + for (i = 0; i < def->ndisks; i++) { + if (STREQ(def->disks[i]->dst, dst)) + return def->disks[i]; + } + + return NULL; +} + + int virDomainDiskInsert(virDomainDefPtr def, virDomainDiskDefPtr disk) { diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index ad8e88c2ee..690aa3c84b 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -3162,6 +3162,10 @@ int virDomainDiskIndexByName(virDomainDefPtr def, const char *name, virDomainDiskDefPtr virDomainDiskByName(virDomainDefPtr def, const char *name, bool allow_ambiguous); +virDomainDiskDefPtr +virDomainDiskByTarget(virDomainDefPtr def, + const char *dst); + int virDomainDiskInsert(virDomainDefPtr def, virDomainDiskDefPtr disk) G_GNUC_WARN_UNUSED_RESULT; diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index e6d40d1cbe..2379c1ed8b 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -330,6 +330,7 @@ virDomainDiskBackingStoreParse; virDomainDiskBusTypeToString; virDomainDiskByAddress; virDomainDiskByName; +virDomainDiskByTarget; virDomainDiskCacheTypeFromString; virDomainDiskCacheTypeToString; virDomainDiskDefAssignAddress;

In both replaced cases we have other code that verifies that the bus can't be changed or that the target is unique, so limiting the search to disks with same bus makes no sense. Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_driver.c | 7 ++----- src/qemu/qemu_hotplug.c | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index bf436f7dc3..ac5f108537 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -7981,12 +7981,9 @@ qemuDomainChangeDiskLive(virDomainObjPtr vm, virDomainDeviceDef oldDev = { .type = dev->type }; int ret = -1; - if (!(orig_disk = virDomainDiskFindByBusAndDst(vm->def, - disk->bus, disk->dst))) { + if (!(orig_disk = virDomainDiskByTarget(vm->def, disk->dst))) { virReportError(VIR_ERR_INTERNAL_ERROR, - _("No device with bus '%s' and target '%s'"), - virDomainDiskBusTypeToString(disk->bus), - disk->dst); + _("disk '%s' not found"), disk->dst); goto cleanup; } diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c index bf301919cc..3cb7f4cddd 100644 --- a/src/qemu/qemu_hotplug.c +++ b/src/qemu/qemu_hotplug.c @@ -1092,7 +1092,7 @@ qemuDomainAttachDeviceDiskLive(virQEMUDriverPtr driver, * for devices supporting media changes */ if ((disk->device == VIR_DOMAIN_DISK_DEVICE_CDROM || disk->device == VIR_DOMAIN_DISK_DEVICE_FLOPPY) && - (orig_disk = virDomainDiskFindByBusAndDst(vm->def, disk->bus, disk->dst))) { + (orig_disk = virDomainDiskByTarget(vm->def, disk->dst))) { if (qemuDomainChangeEjectableMedia(driver, vm, orig_disk, disk->src, false) < 0) return -1; -- 2.21.0

On 10/18/19 1:10 PM, Peter Krempa wrote:
In both replaced cases we have other code that verifies that the bus can't be changed or that the target is unique, so limiting the search to disks with same bus makes no sense.
Signed-off-by: Peter Krempa <pkrempa@redhat.com> ---
Reviewed-by: Daniel Henrique Barboza <danielhb413@gmail.com>
src/qemu/qemu_driver.c | 7 ++----- src/qemu/qemu_hotplug.c | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-)
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index bf436f7dc3..ac5f108537 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -7981,12 +7981,9 @@ qemuDomainChangeDiskLive(virDomainObjPtr vm, virDomainDeviceDef oldDev = { .type = dev->type }; int ret = -1;
- if (!(orig_disk = virDomainDiskFindByBusAndDst(vm->def, - disk->bus, disk->dst))) { + if (!(orig_disk = virDomainDiskByTarget(vm->def, disk->dst))) { virReportError(VIR_ERR_INTERNAL_ERROR, - _("No device with bus '%s' and target '%s'"), - virDomainDiskBusTypeToString(disk->bus), - disk->dst); + _("disk '%s' not found"), disk->dst); goto cleanup; }
diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c index bf301919cc..3cb7f4cddd 100644 --- a/src/qemu/qemu_hotplug.c +++ b/src/qemu/qemu_hotplug.c @@ -1092,7 +1092,7 @@ qemuDomainAttachDeviceDiskLive(virQEMUDriverPtr driver, * for devices supporting media changes */ if ((disk->device == VIR_DOMAIN_DISK_DEVICE_CDROM || disk->device == VIR_DOMAIN_DISK_DEVICE_FLOPPY) && - (orig_disk = virDomainDiskFindByBusAndDst(vm->def, disk->bus, disk->dst))) { + (orig_disk = virDomainDiskByTarget(vm->def, disk->dst))) { if (qemuDomainChangeEjectableMedia(driver, vm, orig_disk, disk->src, false) < 0) return -1;

Previous commit removed last use of this function so we can get rid of it.t Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/conf/domain_conf.c | 21 --------------------- src/conf/domain_conf.h | 3 --- src/libvirt_private.syms | 1 - 3 files changed, 25 deletions(-) diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index 1fffd56a22..e12d676a8c 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -8566,27 +8566,6 @@ virDomainDeviceFindSCSIController(const virDomainDef *def, return NULL; } -virDomainDiskDefPtr -virDomainDiskFindByBusAndDst(virDomainDefPtr def, - int bus, - char *dst) -{ - size_t i; - - if (!dst) - return NULL; - - for (i = 0; i < def->ndisks; i++) { - if (def->disks[i]->bus == bus && - STREQ(def->disks[i]->dst, dst)) { - return def->disks[i]; - } - } - - return NULL; -} - - int virDomainDiskDefAssignAddress(virDomainXMLOptionPtr xmlopt, virDomainDiskDefPtr def, diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index 690aa3c84b..071de9e71e 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -2859,9 +2859,6 @@ void virDomainDiskSetFormat(virDomainDiskDefPtr def, int format); virDomainControllerDefPtr virDomainDeviceFindSCSIController(const virDomainDef *def, virDomainDeviceInfoPtr info); -virDomainDiskDefPtr virDomainDiskFindByBusAndDst(virDomainDefPtr def, - int bus, - char *dst); virDomainControllerDefPtr virDomainControllerDefNew(virDomainControllerType type); void virDomainControllerDefFree(virDomainControllerDefPtr def); diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 2379c1ed8b..fdfe1e7295 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -345,7 +345,6 @@ virDomainDiskDiscardTypeToString; virDomainDiskEmptySource; virDomainDiskErrorPolicyTypeFromString; virDomainDiskErrorPolicyTypeToString; -virDomainDiskFindByBusAndDst; virDomainDiskGeometryTransTypeFromString; virDomainDiskGeometryTransTypeToString; virDomainDiskGetDetectZeroesMode; -- 2.21.0

On 10/18/19 1:10 PM, Peter Krempa wrote:
Previous commit removed last use of this function so we can get rid of it.t
Extra 't' at the end Reviewed-by: Daniel Henrique Barboza <danielhb413@gmail.com>
Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/conf/domain_conf.c | 21 --------------------- src/conf/domain_conf.h | 3 --- src/libvirt_private.syms | 1 - 3 files changed, 25 deletions(-)
diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index 1fffd56a22..e12d676a8c 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -8566,27 +8566,6 @@ virDomainDeviceFindSCSIController(const virDomainDef *def, return NULL; }
-virDomainDiskDefPtr -virDomainDiskFindByBusAndDst(virDomainDefPtr def, - int bus, - char *dst) -{ - size_t i; - - if (!dst) - return NULL; - - for (i = 0; i < def->ndisks; i++) { - if (def->disks[i]->bus == bus && - STREQ(def->disks[i]->dst, dst)) { - return def->disks[i]; - } - } - - return NULL; -} - - int virDomainDiskDefAssignAddress(virDomainXMLOptionPtr xmlopt, virDomainDiskDefPtr def, diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index 690aa3c84b..071de9e71e 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -2859,9 +2859,6 @@ void virDomainDiskSetFormat(virDomainDiskDefPtr def, int format); virDomainControllerDefPtr virDomainDeviceFindSCSIController(const virDomainDef *def, virDomainDeviceInfoPtr info); -virDomainDiskDefPtr virDomainDiskFindByBusAndDst(virDomainDefPtr def, - int bus, - char *dst);
virDomainControllerDefPtr virDomainControllerDefNew(virDomainControllerType type); void virDomainControllerDefFree(virDomainControllerDefPtr def); diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 2379c1ed8b..fdfe1e7295 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -345,7 +345,6 @@ virDomainDiskDiscardTypeToString; virDomainDiskEmptySource; virDomainDiskErrorPolicyTypeFromString; virDomainDiskErrorPolicyTypeToString; -virDomainDiskFindByBusAndDst; virDomainDiskGeometryTransTypeFromString; virDomainDiskGeometryTransTypeToString; virDomainDiskGetDetectZeroesMode;

In many cases we used virDomainDiskByName to solely look up disk by target. We have a new helper now so we can replace it. Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/libxl/libxl_driver.c | 2 +- src/qemu/qemu_blockjob.c | 6 +++--- src/qemu/qemu_domain.c | 4 ++-- src/qemu/qemu_driver.c | 2 +- src/qemu/qemu_migration.c | 3 +-- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/libxl/libxl_driver.c b/src/libxl/libxl_driver.c index 64b21f2073..a08bafe5ce 100644 --- a/src/libxl/libxl_driver.c +++ b/src/libxl/libxl_driver.c @@ -4072,7 +4072,7 @@ libxlDomainUpdateDeviceConfig(virDomainDefPtr vmdef, virDomainDeviceDefPtr dev) switch (dev->type) { case VIR_DOMAIN_DEVICE_DISK: disk = dev->data.disk; - if (!(orig = virDomainDiskByName(vmdef, disk->dst, false))) { + if (!(orig = virDomainDiskByTarget(vmdef, disk->dst))) { virReportError(VIR_ERR_INVALID_ARG, _("target %s doesn't exist."), disk->dst); goto cleanup; diff --git a/src/qemu/qemu_blockjob.c b/src/qemu/qemu_blockjob.c index 3506fa165b..f9b3bdaff4 100644 --- a/src/qemu/qemu_blockjob.c +++ b/src/qemu/qemu_blockjob.c @@ -601,7 +601,7 @@ qemuBlockJobRewriteConfigDiskSource(virDomainObjPtr vm, if (!vm->newDef) return; - if (!(persistDisk = virDomainDiskByName(vm->newDef, disk->dst, false))) + if (!(persistDisk = virDomainDiskByTarget(vm->newDef, disk->dst))) return; if (!virStorageSourceIsSameLocation(disk->src, persistDisk->src)) @@ -839,7 +839,7 @@ qemuBlockJobGetConfigDisk(virDomainObjPtr vm, disksrc = disk->src; - if (!(ret = virDomainDiskByName(vm->newDef, disk->dst, false))) + if (!(ret = virDomainDiskByTarget(vm->newDef, disk->dst))) return NULL; cfgsrc = ret->src; @@ -884,7 +884,7 @@ qemuBlockJobClearConfigChain(virDomainObjPtr vm, if (!vm->newDef || !disk) return; - if (!(cfgdisk = virDomainDiskByName(vm->newDef, disk->dst, false))) + if (!(cfgdisk = virDomainDiskByTarget(vm->newDef, disk->dst))) return; if (!virStorageSourceIsSameLocation(disk->src, cfgdisk->src)) diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index 3deb69cb63..e330d2390c 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -3201,7 +3201,7 @@ qemuDomainObjPrivateXMLParseBlockjobData(virDomainObjPtr vm, invalidData = true; if ((diskdst = virXPathString("string(./disk/@dst)", ctxt)) && - !(disk = virDomainDiskByName(vm->def, diskdst, false))) + !(disk = virDomainDiskByTarget(vm->def, diskdst))) invalidData = true; if ((mirror = virXPathString("string(./disk/@mirror)", ctxt)) && @@ -3374,7 +3374,7 @@ qemuDomainObjPrivateXMLParseJobNBD(virDomainObjPtr vm, virDomainDiskDefPtr disk; if ((dst = virXMLPropString(nodes[i], "dev")) && - (disk = virDomainDiskByName(vm->def, dst, false))) { + (disk = virDomainDiskByTarget(vm->def, dst))) { QEMU_DOMAIN_DISK_PRIVATE(disk)->migrating = true; if (qemuDomainObjPrivateXMLParseJobNBDSource(nodes[i], ctxt, diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index ac5f108537..e50f2277d2 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -15276,7 +15276,7 @@ qemuDomainSnapshotDiskPrepareOne(virQEMUDriverPtr driver, /* modify disk in persistent definition only when the source is the same */ if (vm->newDef && - (persistdisk = virDomainDiskByName(vm->newDef, dd->disk->dst, false)) && + (persistdisk = virDomainDiskByTarget(vm->newDef, dd->disk->dst)) && virStorageSourceIsSameLocation(dd->disk->src, persistdisk->src)) { dd->persistdisk = persistdisk; diff --git a/src/qemu/qemu_migration.c b/src/qemu/qemu_migration.c index c65491f9d6..40aa598832 100644 --- a/src/qemu/qemu_migration.c +++ b/src/qemu/qemu_migration.c @@ -319,8 +319,7 @@ qemuMigrationDstPrecreateStorage(virDomainObjPtr vm, VIR_DEBUG("Looking up disk target '%s' (capacity=%llu)", nbd->disks[i].target, nbd->disks[i].capacity); - if (!(disk = virDomainDiskByName(vm->def, nbd->disks[i].target, - false))) { + if (!(disk = virDomainDiskByTarget(vm->def, nbd->disks[i].target))) { virReportError(VIR_ERR_INTERNAL_ERROR, _("unable to find disk by target: %s"), nbd->disks[i].target); -- 2.21.0

On 10/18/19 1:10 PM, Peter Krempa wrote:
In many cases we used virDomainDiskByName to solely look up disk by target. We have a new helper now so we can replace it.
Signed-off-by: Peter Krempa <pkrempa@redhat.com> ---
Reviewed-by: Daniel Henrique Barboza <danielhb413@gmail.com>
src/libxl/libxl_driver.c | 2 +- src/qemu/qemu_blockjob.c | 6 +++--- src/qemu/qemu_domain.c | 4 ++-- src/qemu/qemu_driver.c | 2 +- src/qemu/qemu_migration.c | 3 +-- 5 files changed, 8 insertions(+), 9 deletions(-)
diff --git a/src/libxl/libxl_driver.c b/src/libxl/libxl_driver.c index 64b21f2073..a08bafe5ce 100644 --- a/src/libxl/libxl_driver.c +++ b/src/libxl/libxl_driver.c @@ -4072,7 +4072,7 @@ libxlDomainUpdateDeviceConfig(virDomainDefPtr vmdef, virDomainDeviceDefPtr dev) switch (dev->type) { case VIR_DOMAIN_DEVICE_DISK: disk = dev->data.disk; - if (!(orig = virDomainDiskByName(vmdef, disk->dst, false))) { + if (!(orig = virDomainDiskByTarget(vmdef, disk->dst))) { virReportError(VIR_ERR_INVALID_ARG, _("target %s doesn't exist."), disk->dst); goto cleanup; diff --git a/src/qemu/qemu_blockjob.c b/src/qemu/qemu_blockjob.c index 3506fa165b..f9b3bdaff4 100644 --- a/src/qemu/qemu_blockjob.c +++ b/src/qemu/qemu_blockjob.c @@ -601,7 +601,7 @@ qemuBlockJobRewriteConfigDiskSource(virDomainObjPtr vm, if (!vm->newDef) return;
- if (!(persistDisk = virDomainDiskByName(vm->newDef, disk->dst, false))) + if (!(persistDisk = virDomainDiskByTarget(vm->newDef, disk->dst))) return;
if (!virStorageSourceIsSameLocation(disk->src, persistDisk->src)) @@ -839,7 +839,7 @@ qemuBlockJobGetConfigDisk(virDomainObjPtr vm,
disksrc = disk->src;
- if (!(ret = virDomainDiskByName(vm->newDef, disk->dst, false))) + if (!(ret = virDomainDiskByTarget(vm->newDef, disk->dst))) return NULL;
cfgsrc = ret->src; @@ -884,7 +884,7 @@ qemuBlockJobClearConfigChain(virDomainObjPtr vm, if (!vm->newDef || !disk) return;
- if (!(cfgdisk = virDomainDiskByName(vm->newDef, disk->dst, false))) + if (!(cfgdisk = virDomainDiskByTarget(vm->newDef, disk->dst))) return;
if (!virStorageSourceIsSameLocation(disk->src, cfgdisk->src)) diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index 3deb69cb63..e330d2390c 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -3201,7 +3201,7 @@ qemuDomainObjPrivateXMLParseBlockjobData(virDomainObjPtr vm, invalidData = true;
if ((diskdst = virXPathString("string(./disk/@dst)", ctxt)) && - !(disk = virDomainDiskByName(vm->def, diskdst, false))) + !(disk = virDomainDiskByTarget(vm->def, diskdst))) invalidData = true;
if ((mirror = virXPathString("string(./disk/@mirror)", ctxt)) && @@ -3374,7 +3374,7 @@ qemuDomainObjPrivateXMLParseJobNBD(virDomainObjPtr vm, virDomainDiskDefPtr disk;
if ((dst = virXMLPropString(nodes[i], "dev")) && - (disk = virDomainDiskByName(vm->def, dst, false))) { + (disk = virDomainDiskByTarget(vm->def, dst))) { QEMU_DOMAIN_DISK_PRIVATE(disk)->migrating = true;
if (qemuDomainObjPrivateXMLParseJobNBDSource(nodes[i], ctxt, diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index ac5f108537..e50f2277d2 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -15276,7 +15276,7 @@ qemuDomainSnapshotDiskPrepareOne(virQEMUDriverPtr driver,
/* modify disk in persistent definition only when the source is the same */ if (vm->newDef && - (persistdisk = virDomainDiskByName(vm->newDef, dd->disk->dst, false)) && + (persistdisk = virDomainDiskByTarget(vm->newDef, dd->disk->dst)) && virStorageSourceIsSameLocation(dd->disk->src, persistdisk->src)) {
dd->persistdisk = persistdisk; diff --git a/src/qemu/qemu_migration.c b/src/qemu/qemu_migration.c index c65491f9d6..40aa598832 100644 --- a/src/qemu/qemu_migration.c +++ b/src/qemu/qemu_migration.c @@ -319,8 +319,7 @@ qemuMigrationDstPrecreateStorage(virDomainObjPtr vm, VIR_DEBUG("Looking up disk target '%s' (capacity=%llu)", nbd->disks[i].target, nbd->disks[i].capacity);
- if (!(disk = virDomainDiskByName(vm->def, nbd->disks[i].target, - false))) { + if (!(disk = virDomainDiskByTarget(vm->def, nbd->disks[i].target))) { virReportError(VIR_ERR_INTERNAL_ERROR, _("unable to find disk by target: %s"), nbd->disks[i].target);

Retrieve data for individual block nodes in a hash table. Currently only capacity and allocation data is extracted but this will be extended in the future. Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_monitor.c | 18 ++++++++++ src/qemu/qemu_monitor.h | 11 ++++++ src/qemu/qemu_monitor_json.c | 69 ++++++++++++++++++++++++++++++++++++ src/qemu/qemu_monitor_json.h | 3 ++ 4 files changed, 101 insertions(+) diff --git a/src/qemu/qemu_monitor.c b/src/qemu/qemu_monitor.c index 86d3800108..b6c890f9ce 100644 --- a/src/qemu/qemu_monitor.c +++ b/src/qemu/qemu_monitor.c @@ -2220,6 +2220,24 @@ qemuMonitorBlockStatsUpdateCapacityBlockdev(qemuMonitorPtr mon, return qemuMonitorJSONBlockStatsUpdateCapacityBlockdev(mon, stats); } + +/** + * qemuMonitorBlockGetNamedNodeData: + * @mon: monitor object + * + * Uses 'query-named-block-nodes' to retrieve information about individual + * storage nodes and returns them in a hash table of qemuBlockNamedNodeDataPtrs + * filled with the data. The hash table keys are node names. + */ +virHashTablePtr +qemuMonitorBlockGetNamedNodeData(qemuMonitorPtr mon) +{ + QEMU_CHECK_MONITOR_NULL(mon); + + return qemuMonitorJSONBlockGetNamedNodeData(mon); +} + + int qemuMonitorBlockResize(qemuMonitorPtr mon, const char *device, diff --git a/src/qemu/qemu_monitor.h b/src/qemu/qemu_monitor.h index 11048dc76a..36eb5f342d 100644 --- a/src/qemu/qemu_monitor.h +++ b/src/qemu/qemu_monitor.h @@ -676,6 +676,17 @@ int qemuMonitorBlockStatsUpdateCapacityBlockdev(qemuMonitorPtr mon, virHashTablePtr stats) ATTRIBUTE_NONNULL(2); + +typedef struct _qemuBlockNamedNodeData qemuBlockNamedNodeData; +typedef qemuBlockNamedNodeData *qemuBlockNamedNodeDataPtr; +struct _qemuBlockNamedNodeData { + unsigned long long capacity; + unsigned long long physical; +}; + +virHashTablePtr +qemuMonitorBlockGetNamedNodeData(qemuMonitorPtr mon); + int qemuMonitorBlockResize(qemuMonitorPtr mon, const char *device, const char *nodename, diff --git a/src/qemu/qemu_monitor_json.c b/src/qemu/qemu_monitor_json.c index 02308dffe6..c474ac0203 100644 --- a/src/qemu/qemu_monitor_json.c +++ b/src/qemu/qemu_monitor_json.c @@ -2879,6 +2879,75 @@ qemuMonitorJSONBlockStatsUpdateCapacityBlockdev(qemuMonitorPtr mon, } +static void +qemuMonitorJSONBlockNamedNodeDataFree(qemuBlockNamedNodeDataPtr data) +{ + if (!data) + return; + + g_free(data); +} +G_DEFINE_AUTOPTR_CLEANUP_FUNC(qemuBlockNamedNodeData, qemuMonitorJSONBlockNamedNodeDataFree); + + +static int +qemuMonitorJSONBlockGetNamedNodeDataWorker(size_t pos G_GNUC_UNUSED, + virJSONValuePtr val, + void *opaque) +{ + virHashTablePtr nodes = opaque; + virJSONValuePtr img; + const char *nodename; + g_autoptr(qemuBlockNamedNodeData) ent = NULL; + + ent = g_new0(qemuBlockNamedNodeData, 1); + + if (!(nodename = virJSONValueObjectGetString(val, "node-name")) || + !(img = virJSONValueObjectGetObject(val, "image"))) + goto broken; + + if (virJSONValueObjectGetNumberUlong(img, "virtual-size", &ent->capacity) < 0) + goto broken; + + /* if actual-size is missing, image is not thin provisioned */ + if (virJSONValueObjectGetNumberUlong(img, "actual-size", &ent->physical) < 0) + ent->physical = ent->capacity; + + if (virHashAddEntry(nodes, nodename, ent) < 0) + return -1; + + ent = NULL; + + return 1; /* we don't want to steal the value from the JSON array */ + + broken: + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("query-named-block-nodes entry was not in expected format")); + return -1; +} + + +virHashTablePtr +qemuMonitorJSONBlockGetNamedNodeData(qemuMonitorPtr mon) +{ + g_autoptr(virJSONValue) nodes = NULL; + g_autoptr(virHashTable) ret = NULL; + + if (!(nodes = qemuMonitorJSONQueryNamedBlockNodes(mon))) + return NULL; + + if (!(ret = virHashNew((virHashDataFreeSimple) qemuMonitorJSONBlockNamedNodeDataFree))) + return NULL; + + if (virJSONValueArrayForeachSteal(nodes, + qemuMonitorJSONBlockGetNamedNodeDataWorker, + ret) < 0) + return NULL; + + return g_steal_pointer(&ret); +} + + int qemuMonitorJSONBlockResize(qemuMonitorPtr mon, const char *device, const char *nodename, diff --git a/src/qemu/qemu_monitor_json.h b/src/qemu/qemu_monitor_json.h index 975de3759a..6ee3e912f9 100644 --- a/src/qemu/qemu_monitor_json.h +++ b/src/qemu/qemu_monitor_json.h @@ -87,6 +87,9 @@ int qemuMonitorJSONBlockStatsUpdateCapacity(qemuMonitorPtr mon, int qemuMonitorJSONBlockStatsUpdateCapacityBlockdev(qemuMonitorPtr mon, virHashTablePtr stats); +virHashTablePtr +qemuMonitorJSONBlockGetNamedNodeData(qemuMonitorPtr mon); + int qemuMonitorJSONBlockResize(qemuMonitorPtr mon, const char *device, const char *nodename, -- 2.21.0

On 10/18/19 11:10 AM, Peter Krempa wrote:
Retrieve data for individual block nodes in a hash table. Currently only capacity and allocation data is extracted but this will be extended in the future.
Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_monitor.c | 18 ++++++++++ src/qemu/qemu_monitor.h | 11 ++++++ src/qemu/qemu_monitor_json.c | 69 ++++++++++++++++++++++++++++++++++++ src/qemu/qemu_monitor_json.h | 3 ++ 4 files changed, 101 insertions(+)
+++ b/src/qemu/qemu_monitor_json.c @@ -2879,6 +2879,75 @@ qemuMonitorJSONBlockStatsUpdateCapacityBlockdev(qemuMonitorPtr mon, }
+static void +qemuMonitorJSONBlockNamedNodeDataFree(qemuBlockNamedNodeDataPtr data) +{ + if (!data) + return; + + g_free(data); +}
g_free(NULL) is a no-op; the short-circuit at the beginning is pointless now (although it might be needed later). Otherwise, ACK. -- Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3226 Virtualization: qemu.org | libvirt.org

Calling the monitor was convenient for the implementation in qemuDomainBlockCopyCommon, but causes the snapshot code to call query-named-block-nodes for every disk. Fix this by removing the monitor call from qemuBlockStorageSourceCreateDetectSize so that the data can be reused in loops. Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_block.c | 26 +++++--------------------- src/qemu/qemu_block.h | 5 ++--- src/qemu/qemu_driver.c | 33 ++++++++++++++++++++++++++++----- 3 files changed, 35 insertions(+), 29 deletions(-) diff --git a/src/qemu/qemu_block.c b/src/qemu/qemu_block.c index 4dc4f2922d..c966b24cb6 100644 --- a/src/qemu/qemu_block.c +++ b/src/qemu/qemu_block.c @@ -2578,10 +2578,9 @@ qemuBlockStorageSourceCreate(virDomainObjPtr vm, /** * qemuBlockStorageSourceCreateDetectSize: - * @vm: domain object + * @blockNamedNodeData: hash table filled with qemuBlockNamedNodeData * @src: storage source to update size/capacity on * @templ: storage source template - * @asyncJob: qemu asynchronous job type * * When creating a storage source via blockdev-create we need to know the size * and capacity of the original volume (e.g. when creating a snapshot or copy). @@ -2589,28 +2588,13 @@ qemuBlockStorageSourceCreate(virDomainObjPtr vm, * to the detected sizes from @templ. */ int -qemuBlockStorageSourceCreateDetectSize(virDomainObjPtr vm, +qemuBlockStorageSourceCreateDetectSize(virHashTablePtr blockNamedNodeData, virStorageSourcePtr src, - virStorageSourcePtr templ, - qemuDomainAsyncJob asyncJob) + virStorageSourcePtr templ) { - qemuDomainObjPrivatePtr priv = vm->privateData; - g_autoptr(virHashTable) stats = NULL; - qemuBlockStatsPtr entry; - int rc; - - if (!(stats = virHashCreate(10, virHashValueFree))) - return -1; - - if (qemuDomainObjEnterMonitorAsync(priv->driver, vm, asyncJob) < 0) - return -1; - - rc = qemuMonitorBlockStatsUpdateCapacityBlockdev(priv->mon, stats); - - if (qemuDomainObjExitMonitor(priv->driver, vm) < 0 || rc < 0) - return -1; + qemuBlockNamedNodeDataPtr entry; - if (!(entry = virHashLookup(stats, templ->nodeformat))) { + if (!(entry = virHashLookup(blockNamedNodeData, templ->nodeformat))) { virReportError(VIR_ERR_INTERNAL_ERROR, _("failed to update capacity data for block node '%s'"), templ->nodeformat); diff --git a/src/qemu/qemu_block.h b/src/qemu/qemu_block.h index be39c9b472..5ddeeaf6e2 100644 --- a/src/qemu/qemu_block.h +++ b/src/qemu/qemu_block.h @@ -194,7 +194,6 @@ qemuBlockStorageSourceCreate(virDomainObjPtr vm, qemuDomainAsyncJob asyncJob); int -qemuBlockStorageSourceCreateDetectSize(virDomainObjPtr vm, +qemuBlockStorageSourceCreateDetectSize(virHashTablePtr blockNamedNodeData, virStorageSourcePtr src, - virStorageSourcePtr templ, - qemuDomainAsyncJob asyncJob); + virStorageSourcePtr templ); diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index e50f2277d2..e908331a93 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -15252,6 +15252,7 @@ qemuDomainSnapshotDiskPrepareOne(virQEMUDriverPtr driver, virDomainDiskDefPtr disk, virDomainSnapshotDiskDefPtr snapdisk, qemuDomainSnapshotDiskDataPtr dd, + virHashTablePtr blockNamedNodeData, bool reuse, bool blockdev, qemuDomainAsyncJob asyncJob) @@ -15355,8 +15356,8 @@ qemuDomainSnapshotDiskPrepareOne(virQEMUDriverPtr driver, if (qemuDomainObjExitMonitor(driver, vm) < 0 || rc < 0) return -1; } else { - if (qemuBlockStorageSourceCreateDetectSize(vm, dd->src, dd->disk->src, - asyncJob) < 0) + if (qemuBlockStorageSourceCreateDetectSize(blockNamedNodeData, + dd->src, dd->disk->src) < 0) return -1; if (qemuBlockStorageSourceCreate(vm, dd->src, dd->disk->src, @@ -15385,6 +15386,7 @@ qemuDomainSnapshotDiskPrepare(virQEMUDriverPtr driver, virQEMUDriverConfigPtr cfg, bool reuse, bool blockdev, + virHashTablePtr blockNamedNodeData, qemuDomainAsyncJob asyncJob, qemuDomainSnapshotDiskDataPtr *rdata, size_t *rndata) @@ -15404,7 +15406,9 @@ qemuDomainSnapshotDiskPrepare(virQEMUDriverPtr driver, if (qemuDomainSnapshotDiskPrepareOne(driver, vm, cfg, vm->def->disks[i], snapdef->disks + i, - data + ndata++, reuse, blockdev, + data + ndata++, + blockNamedNodeData, + reuse, blockdev, asyncJob) < 0) goto cleanup; } @@ -15495,6 +15499,7 @@ qemuDomainSnapshotCreateDiskActive(virQEMUDriverPtr driver, qemuDomainSnapshotDiskDataPtr diskdata = NULL; size_t ndiskdata = 0; bool blockdev = virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BLOCKDEV); + g_autoptr(virHashTable) blockNamedNodeData = NULL; if (virDomainObjCheckActive(vm) < 0) return -1; @@ -15502,10 +15507,21 @@ qemuDomainSnapshotCreateDiskActive(virQEMUDriverPtr driver, if (!(actions = virJSONValueNewArray())) return -1; + if (blockdev) { + if (qemuDomainObjEnterMonitorAsync(driver, vm, asyncJob) < 0) + return -1; + + blockNamedNodeData = qemuMonitorBlockGetNamedNodeData(priv->mon); + + if (qemuDomainObjExitMonitor(driver, vm) < 0 || !blockNamedNodeData) + return -1; + } + /* prepare a list of objects to use in the vm definition so that we don't * have to roll back later */ if (qemuDomainSnapshotDiskPrepare(driver, vm, snap, cfg, reuse, blockdev, - asyncJob, &diskdata, &ndiskdata) < 0) + blockNamedNodeData, asyncJob, + &diskdata, &ndiskdata) < 0) goto cleanup; /* check whether there's anything to do */ @@ -18018,6 +18034,7 @@ qemuDomainBlockCopyCommon(virDomainObjPtr vm, g_autoptr(qemuBlockStorageSourceChainData) crdata = NULL; virStorageSourcePtr n; virStorageSourcePtr mirrorBacking = NULL; + g_autoptr(virHashTable) blockNamedNodeData = NULL; int rc = 0; /* Preliminaries: find the disk we are editing, sanity checks */ @@ -18179,7 +18196,13 @@ qemuDomainBlockCopyCommon(virDomainObjPtr vm, priv->qemuCaps))) goto endjob; } else { - if (qemuBlockStorageSourceCreateDetectSize(vm, mirror, disk->src, QEMU_ASYNC_JOB_NONE) < 0) + qemuDomainObjEnterMonitor(driver, vm); + blockNamedNodeData = qemuMonitorBlockGetNamedNodeData(priv->mon); + if (qemuDomainObjExitMonitor(driver, vm) < 0 || !blockNamedNodeData) + goto endjob; + + if (qemuBlockStorageSourceCreateDetectSize(blockNamedNodeData, + mirror, disk->src)) goto endjob; if (mirror_shallow) { -- 2.21.0

On 10/18/19 11:10 AM, Peter Krempa wrote:
Calling the monitor was convenient for the implementation in qemuDomainBlockCopyCommon, but causes the snapshot code to call query-named-block-nodes for every disk.
Fix this by removing the monitor call from qemuBlockStorageSourceCreateDetectSize so that the data can be reused in loops.
Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_block.c | 26 +++++--------------------- src/qemu/qemu_block.h | 5 ++--- src/qemu/qemu_driver.c | 33 ++++++++++++++++++++++++++++----- 3 files changed, 35 insertions(+), 29 deletions(-)
Nice reduction in monitor calls for guests with lots of disks. ACK -- Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3226 Virtualization: qemu.org | libvirt.org

Delete/merge bitmaps when deleting checkpoints using a 'transaction' so that we don't have to deal with halfway-failed scenarios and also fix access to 'vm' while in the monitor lock. Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_checkpoint.c | 47 +++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/src/qemu/qemu_checkpoint.c b/src/qemu/qemu_checkpoint.c index 4b4ad8bb83..a225f04831 100644 --- a/src/qemu/qemu_checkpoint.c +++ b/src/qemu/qemu_checkpoint.c @@ -133,11 +133,14 @@ qemuCheckpointDiscard(virQEMUDriverPtr driver, if (!metadata_only) { qemuDomainObjPrivatePtr priv = vm->privateData; - bool success = true; bool search_parents; virDomainCheckpointDefPtr chkdef = virDomainCheckpointObjGetDef(chk); + int rc; + g_autoptr(virJSONValue) actions = NULL; + + if (!(actions = virJSONValueNewArray())) + return -1; - qemuDomainObjEnterMonitor(driver, vm); parent = virDomainCheckpointFindByName(vm->checkpoints, chk->def->parent_name); for (i = 0; i < chkdef->ndisks; i++) { @@ -167,31 +170,29 @@ qemuCheckpointDiscard(virQEMUDriverPtr driver, continue; search_parents = false; - arr = virJSONValueNewArray(); - if (!arr || - virJSONValueArrayAppendString(arr, disk->bitmap) < 0) { - success = false; - break; - } - if (chk == virDomainCheckpointGetCurrent(vm->checkpoints) && - qemuMonitorEnableBitmap(priv->mon, node, - disk2->bitmap) < 0) { - success = false; - break; - } - if (qemuMonitorMergeBitmaps(priv->mon, node, - disk2->bitmap, &arr) < 0) { - success = false; - break; + if (!(arr = virJSONValueNewArray())) + return -1; + + if (virJSONValueArrayAppendString(arr, disk->bitmap) < 0) + return -1; + + if (chk == virDomainCheckpointGetCurrent(vm->checkpoints)) { + if (qemuMonitorTransactionBitmapEnable(actions, node, disk2->bitmap) < 0) + return -1; } + + if (qemuMonitorTransactionBitmapMerge(actions, node, disk2->bitmap, &arr) < 0) + return -1; } } - if (qemuMonitorDeleteBitmap(priv->mon, node, disk->bitmap) < 0) { - success = false; - break; - } + + if (qemuMonitorTransactionBitmapRemove(actions, node, disk->bitmap) < 0) + return -1; } - if (qemuDomainObjExitMonitor(driver, vm) < 0 || !success) + + qemuDomainObjEnterMonitor(driver, vm); + rc = qemuMonitorTransaction(priv->mon, &actions); + if (qemuDomainObjExitMonitor(driver, vm) < 0 || rc < 0) return -1; } -- 2.21.0

On 10/18/19 11:11 AM, Peter Krempa wrote:
Delete/merge bitmaps when deleting checkpoints using a 'transaction' so that we don't have to deal with halfway-failed scenarios and also fix access to 'vm' while in the monitor lock.
Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_checkpoint.c | 47 +++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 23 deletions(-)
- if (qemuMonitorDeleteBitmap(priv->mon, node, disk->bitmap) < 0) { - success = false; - break; - } + + if (qemuMonitorTransactionBitmapRemove(actions, node, disk->bitmap) < 0) + return -1;
Transactional bitmap remove depends on a newer qemu than what the pre-patch state required; is this properly gated on capabilities so that you can't get in a scenario where you can create bitmaps but not delete them? Otherwise, the conversion makes sense. -- Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3226 Virtualization: qemu.org | libvirt.org

On Fri, Oct 18, 2019 at 05:29:49PM -0500, Eric Blake wrote:
On 10/18/19 11:11 AM, Peter Krempa wrote:
Delete/merge bitmaps when deleting checkpoints using a 'transaction' so that we don't have to deal with halfway-failed scenarios and also fix access to 'vm' while in the monitor lock.
The whole if (!metadata_only) block looks like a separate function and it has some variables declared in unecessarily big scopes. But the change itself looks good. Reviewed-by: Ján Tomko <jtomko@redhat.com>
Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_checkpoint.c | 47 +++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 23 deletions(-)
- if (qemuMonitorDeleteBitmap(priv->mon, node, disk->bitmap) < 0) { - success = false; - break; - } + + if (qemuMonitorTransactionBitmapRemove(actions, node, disk->bitmap) < 0) + return -1;
Transactional bitmap remove depends on a newer qemu than what the pre-patch state required; is this properly gated on capabilities so that you can't get in a scenario where you can create bitmaps but not delete them?
As of this series, yes :) qemuCheckpointCreateXML requires QEMU_CAPS_INCREMENTAL_BACKUP which is not set even after this series. Jano
Otherwise, the conversion makes sense.
-- Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3226 Virtualization: qemu.org | libvirt.org
-- libvir-list mailing list libvir-list@redhat.com https://www.redhat.com/mailman/listinfo/libvir-list

We replaced them by use of transaction to simplify possible failure scenarios. Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_monitor.c | 51 --------------- src/qemu/qemu_monitor.h | 19 ------ src/qemu/qemu_monitor_json.c | 119 ----------------------------------- src/qemu/qemu_monitor_json.h | 17 ----- tests/qemumonitorjsontest.c | 41 ------------ 5 files changed, 247 deletions(-) diff --git a/src/qemu/qemu_monitor.c b/src/qemu/qemu_monitor.c index b6c890f9ce..8de386817b 100644 --- a/src/qemu/qemu_monitor.c +++ b/src/qemu/qemu_monitor.c @@ -4480,57 +4480,6 @@ qemuMonitorGetCurrentMachineInfo(qemuMonitorPtr mon, } -int -qemuMonitorAddBitmap(qemuMonitorPtr mon, - const char *node, - const char *bitmap, - bool persistent) -{ - VIR_DEBUG("node=%s bitmap=%s persistent=%d", node, bitmap, persistent); - - QEMU_CHECK_MONITOR(mon); - - return qemuMonitorJSONAddBitmap(mon, node, bitmap, persistent); -} - -int -qemuMonitorEnableBitmap(qemuMonitorPtr mon, - const char *node, - const char *bitmap) -{ - VIR_DEBUG("node=%s bitmap=%s", node, bitmap); - - QEMU_CHECK_MONITOR(mon); - - return qemuMonitorJSONEnableBitmap(mon, node, bitmap); -} - -int -qemuMonitorMergeBitmaps(qemuMonitorPtr mon, - const char *node, - const char *dst, - virJSONValuePtr *src) -{ - VIR_DEBUG("node=%s dst=%s", node, dst); - - QEMU_CHECK_MONITOR(mon); - - return qemuMonitorJSONMergeBitmaps(mon, node, dst, src); -} - -int -qemuMonitorDeleteBitmap(qemuMonitorPtr mon, - const char *node, - const char *bitmap) -{ - VIR_DEBUG("node=%s bitmap=%s", node, bitmap); - - QEMU_CHECK_MONITOR(mon); - - return qemuMonitorJSONDeleteBitmap(mon, node, bitmap); -} - - void qemuMonitorJobInfoFree(qemuMonitorJobInfoPtr job) { diff --git a/src/qemu/qemu_monitor.h b/src/qemu/qemu_monitor.h index 36eb5f342d..45f2a5a7d2 100644 --- a/src/qemu/qemu_monitor.h +++ b/src/qemu/qemu_monitor.h @@ -702,25 +702,6 @@ int qemuMonitorSetBalloon(qemuMonitorPtr mon, unsigned long long newmem); int qemuMonitorSetCPU(qemuMonitorPtr mon, int cpu, bool online); -int qemuMonitorAddBitmap(qemuMonitorPtr mon, - const char *node, - const char *bitmap, - bool persistent) - ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3); -int qemuMonitorEnableBitmap(qemuMonitorPtr mon, - const char *node, - const char *bitmap) - ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3); -int qemuMonitorMergeBitmaps(qemuMonitorPtr mon, - const char *node, - const char *dst, - virJSONValuePtr *src) - ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3) ATTRIBUTE_NONNULL(4); -int qemuMonitorDeleteBitmap(qemuMonitorPtr mon, - const char *node, - const char *bitmap) - ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3); - /* XXX should we pass the virDomainDiskDefPtr instead * and hide dev_name details inside monitor. Reconsider diff --git a/src/qemu/qemu_monitor_json.c b/src/qemu/qemu_monitor_json.c index c474ac0203..0062816e3e 100644 --- a/src/qemu/qemu_monitor_json.c +++ b/src/qemu/qemu_monitor_json.c @@ -9128,125 +9128,6 @@ qemuMonitorJSONGetCurrentMachineInfo(qemuMonitorPtr mon, } -int -qemuMonitorJSONAddBitmap(qemuMonitorPtr mon, - const char *node, - const char *bitmap, - bool persistent) -{ - int ret = -1; - virJSONValuePtr cmd; - virJSONValuePtr reply = NULL; - - if (!(cmd = qemuMonitorJSONMakeCommand("block-dirty-bitmap-add", - "s:node", node, - "s:name", bitmap, - "b:persistent", persistent, - NULL))) - return -1; - - if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0) - goto cleanup; - - if (qemuMonitorJSONCheckError(cmd, reply) < 0) - goto cleanup; - - ret = 0; - cleanup: - virJSONValueFree(cmd); - virJSONValueFree(reply); - return ret; -} - -int -qemuMonitorJSONEnableBitmap(qemuMonitorPtr mon, - const char *node, - const char *bitmap) -{ - int ret = -1; - virJSONValuePtr cmd; - virJSONValuePtr reply = NULL; - - if (!(cmd = qemuMonitorJSONMakeCommand("block-dirty-bitmap-enable", - "s:node", node, - "s:name", bitmap, - NULL))) - return -1; - - if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0) - goto cleanup; - - if (qemuMonitorJSONCheckError(cmd, reply) < 0) - goto cleanup; - - ret = 0; - cleanup: - virJSONValueFree(cmd); - virJSONValueFree(reply); - return ret; -} - -int -qemuMonitorJSONMergeBitmaps(qemuMonitorPtr mon, - const char *node, - const char *dst, - virJSONValuePtr *src) -{ - int ret = -1; - virJSONValuePtr cmd; - virJSONValuePtr reply = NULL; - - if (!(cmd = qemuMonitorJSONMakeCommand("block-dirty-bitmap-merge", - "s:node", node, - "s:target", dst, - "a:bitmaps", src, - NULL))) - goto cleanup; - - if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0) - goto cleanup; - - if (qemuMonitorJSONCheckError(cmd, reply) < 0) - goto cleanup; - - ret = 0; - cleanup: - virJSONValueFree(*src); - *src = NULL; - virJSONValueFree(cmd); - virJSONValueFree(reply); - return ret; -} - -int -qemuMonitorJSONDeleteBitmap(qemuMonitorPtr mon, - const char *node, - const char *bitmap) -{ - int ret = -1; - virJSONValuePtr cmd; - virJSONValuePtr reply = NULL; - - if (!(cmd = qemuMonitorJSONMakeCommand("block-dirty-bitmap-remove", - "s:node", node, - "s:name", bitmap, - NULL))) - return -1; - - if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0) - goto cleanup; - - if (qemuMonitorJSONCheckError(cmd, reply) < 0) - goto cleanup; - - ret = 0; - cleanup: - virJSONValueFree(cmd); - virJSONValueFree(reply); - return ret; -} - - int qemuMonitorJSONTransactionBitmapAdd(virJSONValuePtr actions, const char *node, diff --git a/src/qemu/qemu_monitor_json.h b/src/qemu/qemu_monitor_json.h index 6ee3e912f9..d74ef91b83 100644 --- a/src/qemu/qemu_monitor_json.h +++ b/src/qemu/qemu_monitor_json.h @@ -627,23 +627,6 @@ int qemuMonitorJSONGetCurrentMachineInfo(qemuMonitorPtr mon, qemuMonitorCurrentMachineInfoPtr info) ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2); -int qemuMonitorJSONAddBitmap(qemuMonitorPtr mon, - const char *node, - const char *bitmap, - bool persistent); - -int qemuMonitorJSONEnableBitmap(qemuMonitorPtr mon, - const char *node, - const char *bitmap); - -int qemuMonitorJSONMergeBitmaps(qemuMonitorPtr mon, - const char *node, - const char *dst, - virJSONValuePtr *src); - -int qemuMonitorJSONDeleteBitmap(qemuMonitorPtr mon, - const char *node, - const char *bitmap); int qemuMonitorJSONTransactionBitmapAdd(virJSONValuePtr actions, diff --git a/tests/qemumonitorjsontest.c b/tests/qemumonitorjsontest.c index f754e4d94b..4f15d231f9 100644 --- a/tests/qemumonitorjsontest.c +++ b/tests/qemumonitorjsontest.c @@ -1337,9 +1337,6 @@ GEN_TEST_FUNC(qemuMonitorJSONBlockdevTrayOpen, "foodev", true) GEN_TEST_FUNC(qemuMonitorJSONBlockdevTrayClose, "foodev") GEN_TEST_FUNC(qemuMonitorJSONBlockdevMediumRemove, "foodev") GEN_TEST_FUNC(qemuMonitorJSONBlockdevMediumInsert, "foodev", "newnode") -GEN_TEST_FUNC(qemuMonitorJSONAddBitmap, "node", "bitmap", true) -GEN_TEST_FUNC(qemuMonitorJSONEnableBitmap, "node", "bitmap") -GEN_TEST_FUNC(qemuMonitorJSONDeleteBitmap, "node", "bitmap") GEN_TEST_FUNC(qemuMonitorJSONJobDismiss, "jobname") GEN_TEST_FUNC(qemuMonitorJSONJobCancel, "jobname", false) GEN_TEST_FUNC(qemuMonitorJSONJobComplete, "jobname") @@ -1382,40 +1379,6 @@ testQemuMonitorJSONqemuMonitorJSONNBDServerStart(const void *opaque) return 0; } -static int -testQemuMonitorJSONqemuMonitorJSONMergeBitmaps(const void *opaque) -{ - const testGenericData *data = opaque; - virDomainXMLOptionPtr xmlopt = data->xmlopt; - g_autoptr(qemuMonitorTest) test = NULL; - g_autoptr(virJSONValue) arr = NULL; - - if (!(test = qemuMonitorTestNewSchema(xmlopt, data->schema))) - return -1; - - if (!(arr = virJSONValueNewArray())) - return -1; - - if (virJSONValueArrayAppendString(arr, "b1") < 0 || - virJSONValueArrayAppendString(arr, "b2") < 0) - return -1; - - if (qemuMonitorTestAddItem(test, "block-dirty-bitmap-merge", - "{\"return\":{}}") < 0) - return -1; - - if (qemuMonitorJSONMergeBitmaps(qemuMonitorTestGetMonitor(test), - "node", "dst", &arr) < 0) - return -1; - - if (arr) { - VIR_TEST_VERBOSE("arr should have been cleared"); - return -1; - } - - return 0; -} - static bool testQemuMonitorJSONqemuMonitorJSONQueryCPUsEqual(struct qemuMonitorQueryCpusEntry *a, struct qemuMonitorQueryCpusEntry *b) @@ -3174,9 +3137,6 @@ mymain(void) DO_TEST_GEN(qemuMonitorJSONBlockdevTrayClose); DO_TEST_GEN(qemuMonitorJSONBlockdevMediumRemove); DO_TEST_GEN(qemuMonitorJSONBlockdevMediumInsert); - DO_TEST_GEN(qemuMonitorJSONAddBitmap); - DO_TEST_GEN(qemuMonitorJSONEnableBitmap); - DO_TEST_GEN(qemuMonitorJSONDeleteBitmap); DO_TEST_GEN(qemuMonitorJSONJobDismiss); DO_TEST_GEN(qemuMonitorJSONJobCancel); DO_TEST_GEN(qemuMonitorJSONJobComplete); @@ -3196,7 +3156,6 @@ mymain(void) DO_TEST(qemuMonitorJSONSendKeyHoldtime); DO_TEST(qemuMonitorSupportsActiveCommit); DO_TEST(qemuMonitorJSONNBDServerStart); - DO_TEST(qemuMonitorJSONMergeBitmaps); DO_TEST_CPU_DATA("host"); DO_TEST_CPU_DATA("full"); -- 2.21.0

On Fri, Oct 18, 2019 at 06:11:01PM +0200, Peter Krempa wrote:
We replaced them by use of transaction to simplify possible failure scenarios.
Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_monitor.c | 51 --------------- src/qemu/qemu_monitor.h | 19 ------ src/qemu/qemu_monitor_json.c | 119 ----------------------------------- src/qemu/qemu_monitor_json.h | 17 ----- tests/qemumonitorjsontest.c | 41 ------------ 5 files changed, 247 deletions(-)
Reviewed-by: Ján Tomko <jtomko@redhat.com> Jano

There's no point in clearing the current snapshot when we are just changing the definition of the current snapshot as by the virtue of the 'update_current' flag the same snapshot would become current in qemuDomainSnapshotCreateXML. Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/conf/snapshot_conf.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/conf/snapshot_conf.c b/src/conf/snapshot_conf.c index 0e9d307321..ec47e14955 100644 --- a/src/conf/snapshot_conf.c +++ b/src/conf/snapshot_conf.c @@ -989,7 +989,7 @@ virDomainSnapshotRedefinePrep(virDomainObjPtr vm, virDomainSnapshotDefPtr *defptr, virDomainMomentObjPtr *snap, virDomainXMLOptionPtr xmlopt, - bool *update_current, + bool *update_current G_GNUC_UNUSED, unsigned int flags) { virDomainSnapshotDefPtr def = *defptr; @@ -1012,11 +1012,6 @@ virDomainSnapshotRedefinePrep(virDomainObjPtr vm, return -1; } if (other) { - if (other == virDomainSnapshotGetCurrent(vm->snapshots)) { - *update_current = true; - virDomainSnapshotSetCurrent(vm->snapshots, NULL); - } - /* Drop and rebuild the parent relationship, but keep all * child relations by reusing snap. */ virDomainMomentDropParent(other); -- 2.21.0

On Fri, Oct 18, 2019 at 06:11:02PM +0200, Peter Krempa wrote:
There's no point in clearing the current snapshot when we are just changing the definition of the current snapshot as by the virtue of the 'update_current' flag the same snapshot would become current in qemuDomainSnapshotCreateXML.
Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/conf/snapshot_conf.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-)
diff --git a/src/conf/snapshot_conf.c b/src/conf/snapshot_conf.c index 0e9d307321..ec47e14955 100644 --- a/src/conf/snapshot_conf.c +++ b/src/conf/snapshot_conf.c @@ -989,7 +989,7 @@ virDomainSnapshotRedefinePrep(virDomainObjPtr vm, virDomainSnapshotDefPtr *defptr, virDomainMomentObjPtr *snap, virDomainXMLOptionPtr xmlopt, - bool *update_current, + bool *update_current G_GNUC_UNUSED, unsigned int flags) { virDomainSnapshotDefPtr def = *defptr;
Reviewed-by: Ján Tomko <jtomko@redhat.com> Jano

The variable is unused so we can drop it. Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/conf/snapshot_conf.c | 1 - src/conf/snapshot_conf.h | 1 - src/qemu/qemu_driver.c | 2 +- src/test/test_driver.c | 2 +- 4 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/conf/snapshot_conf.c b/src/conf/snapshot_conf.c index ec47e14955..a1d14a480a 100644 --- a/src/conf/snapshot_conf.c +++ b/src/conf/snapshot_conf.c @@ -989,7 +989,6 @@ virDomainSnapshotRedefinePrep(virDomainObjPtr vm, virDomainSnapshotDefPtr *defptr, virDomainMomentObjPtr *snap, virDomainXMLOptionPtr xmlopt, - bool *update_current G_GNUC_UNUSED, unsigned int flags) { virDomainSnapshotDefPtr def = *defptr; diff --git a/src/conf/snapshot_conf.h b/src/conf/snapshot_conf.h index 7e2ffa9d60..b0b52e6a34 100644 --- a/src/conf/snapshot_conf.h +++ b/src/conf/snapshot_conf.h @@ -136,7 +136,6 @@ int virDomainSnapshotRedefinePrep(virDomainObjPtr vm, virDomainSnapshotDefPtr *def, virDomainMomentObjPtr *snap, virDomainXMLOptionPtr xmlopt, - bool *update_current, unsigned int flags); int virDomainSnapshotRedefineValidate(virDomainSnapshotDefPtr def, diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index e908331a93..501c909e49 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -15918,7 +15918,7 @@ qemuDomainSnapshotCreateXML(virDomainPtr domain, if (redefine) { if (virDomainSnapshotRedefinePrep(vm, &def, &snap, driver->xmlopt, - &update_current, flags) < 0) + flags) < 0) goto endjob; } else { /* Easiest way to clone inactive portion of vm->def is via diff --git a/src/test/test_driver.c b/src/test/test_driver.c index ab7e5fc02a..feada30a4b 100644 --- a/src/test/test_driver.c +++ b/src/test/test_driver.c @@ -8614,7 +8614,7 @@ testDomainSnapshotCreateXML(virDomainPtr domain, if (redefine) { if (virDomainSnapshotRedefinePrep(vm, &def, &snap, privconn->xmlopt, - &update_current, flags) < 0) + flags) < 0) goto cleanup; } else { if (!(def->parent.dom = virDomainDefCopy(vm->def, -- 2.21.0

On Fri, Oct 18, 2019 at 06:11:03PM +0200, Peter Krempa wrote:
The variable is unused so we can drop it.
Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/conf/snapshot_conf.c | 1 - src/conf/snapshot_conf.h | 1 - src/qemu/qemu_driver.c | 2 +- src/test/test_driver.c | 2 +- 4 files changed, 2 insertions(+), 4 deletions(-)
Reviewed-by: Ján Tomko <jtomko@redhat.com> Jano

On 10/18/19 11:11 AM, Peter Krempa wrote:
The variable is unused so we can drop it.
Signed-off-by: Peter Krempa <pkrempa@redhat.com> ---
Merging 17 and 18 might make it easier to review (proof who the callers were, to audit that all callers will manage to update the current snapshot without help from the parameter). At any rate, whether 1 or 2 patches, ACK. -- Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3226 Virtualization: qemu.org | libvirt.org

The 'other' variable was used to store the parent of the redefined checkpoint and then the existing version of the currently redefined checkpoint. Make it less confusing by adding a 'parent' variable for the first case. Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/conf/checkpoint_conf.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/conf/checkpoint_conf.c b/src/conf/checkpoint_conf.c index b254dce7fd..ea2e77a50a 100644 --- a/src/conf/checkpoint_conf.c +++ b/src/conf/checkpoint_conf.c @@ -540,6 +540,7 @@ virDomainCheckpointRedefinePrep(virDomainObjPtr vm, { virDomainCheckpointDefPtr def = *defptr; char uuidstr[VIR_UUID_STRING_BUFLEN]; + virDomainMomentObjPtr parent = NULL; virDomainMomentObjPtr other = NULL; virDomainCheckpointDefPtr otherdef = NULL; @@ -558,12 +559,13 @@ virDomainCheckpointRedefinePrep(virDomainObjPtr vm, if (virDomainCheckpointAlignDisks(def) < 0) return -1; - if (def->parent.parent_name) - other = virDomainCheckpointFindByName(vm->checkpoints, - def->parent.parent_name); - if (other == virDomainCheckpointGetCurrent(vm->checkpoints)) { - *update_current = true; - virDomainCheckpointSetCurrent(vm->checkpoints, NULL); + if (def->parent.parent_name && + (parent = virDomainCheckpointFindByName(vm->checkpoints, + def->parent.parent_name))) { + if (parent == virDomainCheckpointGetCurrent(vm->checkpoints)) { + *update_current = true; + virDomainCheckpointSetCurrent(vm->checkpoints, NULL); + } } other = virDomainCheckpointFindByName(vm->checkpoints, def->parent.name); -- 2.21.0

On Fri, Oct 18, 2019 at 06:11:04PM +0200, Peter Krempa wrote:
The 'other' variable was used to store the parent of the redefined checkpoint and then the existing version of the currently redefined checkpoint. Make it less confusing by adding a 'parent' variable for the first case.
Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/conf/checkpoint_conf.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-)
Reviewed-by: Ján Tomko <jtomko@redhat.com> Jano

There's no point in clearing the current checkpoint when we are just changing the definition of the current checkpoint as by the virtue of the 'update_current' flag the same checkpoint would become current in qemuCheckpointCreateXML. Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/conf/checkpoint_conf.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/conf/checkpoint_conf.c b/src/conf/checkpoint_conf.c index ea2e77a50a..7f2cfd7f4c 100644 --- a/src/conf/checkpoint_conf.c +++ b/src/conf/checkpoint_conf.c @@ -575,11 +575,6 @@ virDomainCheckpointRedefinePrep(virDomainObjPtr vm, def->parent.dom, xmlopt)) return -1; - if (other == virDomainCheckpointGetCurrent(vm->checkpoints)) { - *update_current = true; - virDomainCheckpointSetCurrent(vm->checkpoints, NULL); - } - /* Drop and rebuild the parent relationship, but keep all * child relations by reusing chk. */ virDomainMomentDropParent(other); -- 2.21.0

On Fri, Oct 18, 2019 at 06:11:05PM +0200, Peter Krempa wrote:
There's no point in clearing the current checkpoint when we are just changing the definition of the current checkpoint as by the virtue of the 'update_current' flag the same checkpoint would become current in qemuCheckpointCreateXML.
Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/conf/checkpoint_conf.c | 5 ----- 1 file changed, 5 deletions(-)
Reviewed-by: Ján Tomko <jtomko@redhat.com> Jano

If we are updating the current checkpoint when redefining by mentioning the current checkpoint as a parent of the newly redefined one we don't have to clear it first. Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/conf/checkpoint_conf.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/conf/checkpoint_conf.c b/src/conf/checkpoint_conf.c index 7f2cfd7f4c..cbff2a0db0 100644 --- a/src/conf/checkpoint_conf.c +++ b/src/conf/checkpoint_conf.c @@ -562,10 +562,8 @@ virDomainCheckpointRedefinePrep(virDomainObjPtr vm, if (def->parent.parent_name && (parent = virDomainCheckpointFindByName(vm->checkpoints, def->parent.parent_name))) { - if (parent == virDomainCheckpointGetCurrent(vm->checkpoints)) { + if (parent == virDomainCheckpointGetCurrent(vm->checkpoints)) *update_current = true; - virDomainCheckpointSetCurrent(vm->checkpoints, NULL); - } } other = virDomainCheckpointFindByName(vm->checkpoints, def->parent.name); -- 2.21.0

On Fri, Oct 18, 2019 at 06:11:06PM +0200, Peter Krempa wrote:
If we are updating the current checkpoint when redefining by mentioning the current checkpoint as a parent of the newly redefined one we don't have to clear it first.
Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/conf/checkpoint_conf.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-)
Reviewed-by: Ján Tomko <jtomko@redhat.com> Jano

Prevent insane configurations by enforcing that disk bitmap for a checkpoint must match the name of the checkpoint. Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_checkpoint.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/qemu/qemu_checkpoint.c b/src/qemu/qemu_checkpoint.c index a225f04831..54719e7f5c 100644 --- a/src/qemu/qemu_checkpoint.c +++ b/src/qemu/qemu_checkpoint.c @@ -271,6 +271,13 @@ qemuCheckpointPrepare(virQEMUDriverPtr driver, if (disk->type != VIR_DOMAIN_CHECKPOINT_TYPE_BITMAP) continue; + if (STRNEQ(disk->bitmap, def->parent.name)) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("bitmap for disk '%s' must match checkpoint name '%s'"), + disk->name, def->parent.name); + goto cleanup; + } + if (vm->def->disks[i]->src->format != VIR_STORAGE_FILE_QCOW2) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("checkpoint for disk %s unsupported " -- 2.21.0

On Fri, Oct 18, 2019 at 06:11:07PM +0200, Peter Krempa wrote:
Prevent insane configurations by enforcing that disk bitmap for a checkpoint must match the name of the checkpoint.
Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_checkpoint.c | 7 +++++++ 1 file changed, 7 insertions(+)
Reviewed-by: Ján Tomko <jtomko@redhat.com> Jano

Separate out individual steps of creating a checkpoint from qemuCheckpointCreateXML into separate functions. This makes the function more readable and understandable and also some of the new functions will be reusable when we will be creating a checkpoint along with a backup in the upcoming patches. Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_checkpoint.c | 160 ++++++++++++++++++++++++------------- src/qemu/qemu_checkpoint.h | 8 ++ 2 files changed, 111 insertions(+), 57 deletions(-) diff --git a/src/qemu/qemu_checkpoint.c b/src/qemu/qemu_checkpoint.c index 54719e7f5c..946ae78368 100644 --- a/src/qemu/qemu_checkpoint.c +++ b/src/qemu/qemu_checkpoint.c @@ -348,6 +348,90 @@ qemuCheckpointAddActions(virDomainObjPtr vm, } +static virDomainMomentObjPtr +qemuCheckpointRedefine(virQEMUDriverPtr driver, + virDomainObjPtr vm, + virDomainCheckpointDefPtr *def, + bool *update_current) +{ + virDomainMomentObjPtr chk = NULL; + + if (virDomainCheckpointRedefinePrep(vm, def, &chk, driver->xmlopt, + update_current) < 0) + return NULL; + + /* XXX Should we validate that the redefined checkpoint even + * makes sense, such as checking that qemu-img recognizes the + * checkpoint bitmap name in at least one of the domain's disks? */ + + if (chk) + return chk; + + chk = virDomainCheckpointAssignDef(vm->checkpoints, *def); + *def = NULL; + return chk; +} + + +int +qemuCheckpointCreateCommon(virQEMUDriverPtr driver, + virDomainObjPtr vm, + virCapsPtr caps, + virDomainCheckpointDefPtr *def, + virJSONValuePtr *actions, + virDomainMomentObjPtr *chk) +{ + g_autoptr(virJSONValue) tmpactions = NULL; + virDomainMomentObjPtr parent; + + if (qemuCheckpointPrepare(driver, caps, vm, *def) < 0) + return -1; + + if ((parent = virDomainCheckpointGetCurrent(vm->checkpoints))) { + if (VIR_STRDUP((*def)->parent.parent_name, parent->def->name) < 0) + return -1; + } + + if (!(tmpactions = virJSONValueNewArray())) + return -1; + + if (qemuCheckpointAddActions(vm, tmpactions, parent, *def) < 0) + return -1; + + if (!(*chk = virDomainCheckpointAssignDef(vm->checkpoints, *def))) + return -1; + + *def = NULL; + + *actions = g_steal_pointer(&tmpactions); + return 0; +} + + +static virDomainMomentObjPtr +qemuCheckpointCreate(virQEMUDriverPtr driver, + virDomainObjPtr vm, + virCapsPtr caps, + virDomainCheckpointDefPtr *def) +{ + g_autoptr(virJSONValue) actions = NULL; + virDomainMomentObjPtr chk = NULL; + int rc; + + if (qemuCheckpointCreateCommon(driver, vm, caps, def, &actions, &chk) < 0) + return NULL; + + qemuDomainObjEnterMonitor(driver, vm); + rc = qemuMonitorTransaction(qemuDomainGetMonitor(vm), &actions); + if (qemuDomainObjExitMonitor(driver, vm) < 0 || rc < 0) { + virDomainCheckpointObjListRemove(vm->checkpoints, chk); + return NULL; + } + + return chk; +} + + virDomainCheckpointPtr qemuCheckpointCreateXML(virDomainPtr domain, virDomainObjPtr vm, @@ -361,11 +445,8 @@ qemuCheckpointCreateXML(virDomainPtr domain, bool update_current = true; bool redefine = flags & VIR_DOMAIN_CHECKPOINT_CREATE_REDEFINE; unsigned int parse_flags = 0; - virDomainMomentObjPtr other = NULL; g_autoptr(virQEMUDriverConfig) cfg = NULL; g_autoptr(virCaps) caps = NULL; - g_autoptr(virJSONValue) actions = NULL; - int ret; g_autoptr(virDomainCheckpointDef) def = NULL; virCheckFlags(VIR_DOMAIN_CHECKPOINT_CREATE_REDEFINE, NULL); @@ -400,44 +481,30 @@ qemuCheckpointCreateXML(virDomainPtr domain, return NULL; if (redefine) { - if (virDomainCheckpointRedefinePrep(vm, &def, &chk, - driver->xmlopt, - &update_current) < 0) - goto endjob; - } else if (qemuCheckpointPrepare(driver, caps, vm, def) < 0) { - goto endjob; + chk = qemuCheckpointRedefine(driver, vm, &def, &update_current); + } else { + chk = qemuCheckpointCreate(driver, vm, caps, &def); } - if (!chk) { - if (!(chk = virDomainCheckpointAssignDef(vm->checkpoints, def))) - goto endjob; - - def = NULL; - } + if (!chk) + goto endjob; - other = virDomainCheckpointGetCurrent(vm->checkpoints); - if (other) { - if (!redefine && - VIR_STRDUP(chk->def->parent_name, other->def->name) < 0) - goto endjob; + if (update_current) + virDomainCheckpointSetCurrent(vm->checkpoints, chk); + + if (qemuCheckpointWriteMetadata(vm, chk, driver->caps, + driver->xmlopt, + cfg->checkpointDir) < 0) { + /* if writing of metadata fails, error out rather than trying + * to silently carry on without completing the checkpoint */ + virReportError(VIR_ERR_INTERNAL_ERROR, + _("unable to save metadata for checkpoint %s"), + chk->def->name); + virDomainCheckpointObjListRemove(vm->checkpoints, chk); + goto endjob; } - /* actually do the checkpoint */ - if (redefine) { - /* XXX Should we validate that the redefined checkpoint even - * makes sense, such as checking that qemu-img recognizes the - * checkpoint bitmap name in at least one of the domain's disks? */ - } else { - if (!(actions = virJSONValueNewArray())) - goto endjob; - if (qemuCheckpointAddActions(vm, actions, other, - virDomainCheckpointObjGetDef(chk)) < 0) - goto endjob; - qemuDomainObjEnterMonitor(driver, vm); - ret = qemuMonitorTransaction(priv->mon, &actions); - if (qemuDomainObjExitMonitor(driver, vm) < 0 || ret < 0) - goto endjob; - } + virDomainCheckpointLinkParent(vm->checkpoints, chk); /* If we fail after this point, there's not a whole lot we can do; * we've successfully created the checkpoint, so we have to go @@ -446,27 +513,6 @@ qemuCheckpointCreateXML(virDomainPtr domain, checkpoint = virGetDomainCheckpoint(domain, chk->def->name); endjob: - if (checkpoint) { - if (update_current) - virDomainCheckpointSetCurrent(vm->checkpoints, chk); - if (qemuCheckpointWriteMetadata(vm, chk, driver->caps, - driver->xmlopt, - cfg->checkpointDir) < 0) { - /* if writing of metadata fails, error out rather than trying - * to silently carry on without completing the checkpoint */ - virObjectUnref(checkpoint); - checkpoint = NULL; - virReportError(VIR_ERR_INTERNAL_ERROR, - _("unable to save metadata for checkpoint %s"), - chk->def->name); - virDomainCheckpointObjListRemove(vm->checkpoints, chk); - } else { - virDomainCheckpointLinkParent(vm->checkpoints, chk); - } - } else if (chk) { - virDomainCheckpointObjListRemove(vm->checkpoints, chk); - } - qemuDomainObjEndJob(driver, vm); return checkpoint; diff --git a/src/qemu/qemu_checkpoint.h b/src/qemu/qemu_checkpoint.h index 49f51b1fdd..7fcbc99541 100644 --- a/src/qemu/qemu_checkpoint.h +++ b/src/qemu/qemu_checkpoint.h @@ -53,3 +53,11 @@ int qemuCheckpointDelete(virDomainObjPtr vm, virDomainCheckpointPtr checkpoint, unsigned int flags); + +int +qemuCheckpointCreateCommon(virQEMUDriverPtr driver, + virDomainObjPtr vm, + virCapsPtr caps, + virDomainCheckpointDefPtr *def, + virJSONValuePtr *actions, + virDomainMomentObjPtr *chk); -- 2.21.0

On Fri, Oct 18, 2019 at 06:11:08PM +0200, Peter Krempa wrote:
Separate out individual steps of creating a checkpoint from qemuCheckpointCreateXML into separate functions. This makes the function more readable and understandable and also some of the new functions will be reusable when we will be creating a checkpoint along with a backup in the upcoming patches.
Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_checkpoint.c | 160 ++++++++++++++++++++++++------------- src/qemu/qemu_checkpoint.h | 8 ++ 2 files changed, 111 insertions(+), 57 deletions(-)
diff --git a/src/qemu/qemu_checkpoint.c b/src/qemu/qemu_checkpoint.c index 54719e7f5c..946ae78368 100644 --- a/src/qemu/qemu_checkpoint.c +++ b/src/qemu/qemu_checkpoint.c @@ -348,6 +348,90 @@ qemuCheckpointAddActions(virDomainObjPtr vm, }
+static virDomainMomentObjPtr +qemuCheckpointRedefine(virQEMUDriverPtr driver, + virDomainObjPtr vm, + virDomainCheckpointDefPtr *def, + bool *update_current) +{ + virDomainMomentObjPtr chk = NULL; + + if (virDomainCheckpointRedefinePrep(vm, def, &chk, driver->xmlopt, + update_current) < 0) + return NULL; + + /* XXX Should we validate that the redefined checkpoint even + * makes sense, such as checking that qemu-img recognizes the + * checkpoint bitmap name in at least one of the domain's disks? */ + + if (chk) + return chk; + + chk = virDomainCheckpointAssignDef(vm->checkpoints, *def); + *def = NULL; + return chk; +} + + +int +qemuCheckpointCreateCommon(virQEMUDriverPtr driver, + virDomainObjPtr vm, + virCapsPtr caps, + virDomainCheckpointDefPtr *def, + virJSONValuePtr *actions, + virDomainMomentObjPtr *chk) +{ + g_autoptr(virJSONValue) tmpactions = NULL; + virDomainMomentObjPtr parent; + + if (qemuCheckpointPrepare(driver, caps, vm, *def) < 0) + return -1; + + if ((parent = virDomainCheckpointGetCurrent(vm->checkpoints))) { + if (VIR_STRDUP((*def)->parent.parent_name, parent->def->name) < 0) + return -1;
g_strdup
+ } +
Reviewed-by: Ján Tomko <jtomko@redhat.com> Jano

Extract the linking and saving bits of checkpoint creation into qemuCheckpointCreateFinalize so that qemuCheckpointCreateXML is a bit simpler and also makes it reusable in the backup code. Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_checkpoint.c | 44 +++++++++++++++++++++++++------------- src/qemu/qemu_checkpoint.h | 7 ++++++ 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/src/qemu/qemu_checkpoint.c b/src/qemu/qemu_checkpoint.c index 946ae78368..6a272f9dab 100644 --- a/src/qemu/qemu_checkpoint.c +++ b/src/qemu/qemu_checkpoint.c @@ -432,6 +432,34 @@ qemuCheckpointCreate(virQEMUDriverPtr driver, } +int +qemuCheckpointCreateFinalize(virQEMUDriverPtr driver, + virDomainObjPtr vm, + virQEMUDriverConfigPtr cfg, + virDomainMomentObjPtr chk, + bool update_current) +{ + if (update_current) + virDomainCheckpointSetCurrent(vm->checkpoints, chk); + + if (qemuCheckpointWriteMetadata(vm, chk, driver->caps, + driver->xmlopt, + cfg->checkpointDir) < 0) { + /* if writing of metadata fails, error out rather than trying + * to silently carry on without completing the checkpoint */ + virReportError(VIR_ERR_INTERNAL_ERROR, + _("unable to save metadata for checkpoint %s"), + chk->def->name); + virDomainCheckpointObjListRemove(vm->checkpoints, chk); + return -1; + } + + virDomainCheckpointLinkParent(vm->checkpoints, chk); + + return 0; +} + + virDomainCheckpointPtr qemuCheckpointCreateXML(virDomainPtr domain, virDomainObjPtr vm, @@ -489,22 +517,8 @@ qemuCheckpointCreateXML(virDomainPtr domain, if (!chk) goto endjob; - if (update_current) - virDomainCheckpointSetCurrent(vm->checkpoints, chk); - - if (qemuCheckpointWriteMetadata(vm, chk, driver->caps, - driver->xmlopt, - cfg->checkpointDir) < 0) { - /* if writing of metadata fails, error out rather than trying - * to silently carry on without completing the checkpoint */ - virReportError(VIR_ERR_INTERNAL_ERROR, - _("unable to save metadata for checkpoint %s"), - chk->def->name); - virDomainCheckpointObjListRemove(vm->checkpoints, chk); + if (qemuCheckpointCreateFinalize(driver, vm, cfg, chk, update_current) < 0) goto endjob; - } - - virDomainCheckpointLinkParent(vm->checkpoints, chk); /* If we fail after this point, there's not a whole lot we can do; * we've successfully created the checkpoint, so we have to go diff --git a/src/qemu/qemu_checkpoint.h b/src/qemu/qemu_checkpoint.h index 7fcbc99541..d0ea8f000f 100644 --- a/src/qemu/qemu_checkpoint.h +++ b/src/qemu/qemu_checkpoint.h @@ -61,3 +61,10 @@ qemuCheckpointCreateCommon(virQEMUDriverPtr driver, virDomainCheckpointDefPtr *def, virJSONValuePtr *actions, virDomainMomentObjPtr *chk); + +int +qemuCheckpointCreateFinalize(virQEMUDriverPtr driver, + virDomainObjPtr vm, + virQEMUDriverConfigPtr cfg, + virDomainMomentObjPtr chk, + bool update_current); -- 2.21.0

On Fri, Oct 18, 2019 at 06:11:09PM +0200, Peter Krempa wrote:
Extract the linking and saving bits of checkpoint creation into qemuCheckpointCreateFinalize so that qemuCheckpointCreateXML is a bit simpler and also makes it reusable in the backup code.
Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_checkpoint.c | 44 +++++++++++++++++++++++++------------- src/qemu/qemu_checkpoint.h | 7 ++++++ 2 files changed, 36 insertions(+), 15 deletions(-)
Reviewed-by: Ján Tomko <jtomko@redhat.com> Jano

Introduce qemuMonitorTransactionBitmapMergeSourceAddBitmap which adds the appropriate entry into a virJSONValue array to be used with qemuMonitorTransactionBitmapMerge. Bitmap merging supports two possible formats and this new helper implements the more universal one specifying also the source node name. In addition use the new helper in the testQemuMonitorJSONTransaction test. Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_monitor.c | 9 +++++++++ src/qemu/qemu_monitor.h | 4 ++++ src/qemu/qemu_monitor_json.c | 21 +++++++++++++++++++++ src/qemu/qemu_monitor_json.h | 5 +++++ tests/qemumonitorjsontest.c | 4 ++-- 5 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/qemu/qemu_monitor.c b/src/qemu/qemu_monitor.c index 8de386817b..57229a68c0 100644 --- a/src/qemu/qemu_monitor.c +++ b/src/qemu/qemu_monitor.c @@ -4551,6 +4551,15 @@ qemuMonitorTransactionBitmapMerge(virJSONValuePtr actions, } +int +qemuMonitorTransactionBitmapMergeSourceAddBitmap(virJSONValuePtr sources, + const char *sourcenode, + const char *sourcebitmap) +{ + return qemuMonitorJSONTransactionBitmapMergeSourceAddBitmap(sources, sourcenode, sourcebitmap); +} + + int qemuMonitorTransactionSnapshotLegacy(virJSONValuePtr actions, const char *device, diff --git a/src/qemu/qemu_monitor.h b/src/qemu/qemu_monitor.h index 45f2a5a7d2..a1c980e40e 100644 --- a/src/qemu/qemu_monitor.h +++ b/src/qemu/qemu_monitor.h @@ -1364,6 +1364,10 @@ qemuMonitorTransactionBitmapMerge(virJSONValuePtr actions, const char *node, const char *target, virJSONValuePtr *sources); +int +qemuMonitorTransactionBitmapMergeSourceAddBitmap(virJSONValuePtr sources, + const char *sourcenode, + const char *sourcebitmap); int qemuMonitorTransactionSnapshotLegacy(virJSONValuePtr actions, diff --git a/src/qemu/qemu_monitor_json.c b/src/qemu/qemu_monitor_json.c index 0062816e3e..545911069e 100644 --- a/src/qemu/qemu_monitor_json.c +++ b/src/qemu/qemu_monitor_json.c @@ -9199,6 +9199,27 @@ qemuMonitorJSONTransactionBitmapMerge(virJSONValuePtr actions, } +int +qemuMonitorJSONTransactionBitmapMergeSourceAddBitmap(virJSONValuePtr sources, + const char *sourcenode, + const char *sourcebitmap) +{ + g_autoptr(virJSONValue) sourceobj = NULL; + + if (virJSONValueObjectCreate(&sourceobj, + "s:node", sourcenode, + "s:name", sourcebitmap, + NULL) < 0) + return -1; + + if (virJSONValueArrayAppend(sources, sourceobj) < 0) + return -1; + + sourceobj = NULL; + return 0; +} + + int qemuMonitorJSONTransactionSnapshotLegacy(virJSONValuePtr actions, const char *device, diff --git a/src/qemu/qemu_monitor_json.h b/src/qemu/qemu_monitor_json.h index d74ef91b83..6617783797 100644 --- a/src/qemu/qemu_monitor_json.h +++ b/src/qemu/qemu_monitor_json.h @@ -652,6 +652,11 @@ qemuMonitorJSONTransactionBitmapMerge(virJSONValuePtr actions, const char *target, virJSONValuePtr *sources); +int +qemuMonitorJSONTransactionBitmapMergeSourceAddBitmap(virJSONValuePtr sources, + const char *sourcenode, + const char *sourcebitmap); + int qemuMonitorJSONTransactionSnapshotLegacy(virJSONValuePtr actions, const char *device, diff --git a/tests/qemumonitorjsontest.c b/tests/qemumonitorjsontest.c index 4f15d231f9..cefa0f08cf 100644 --- a/tests/qemumonitorjsontest.c +++ b/tests/qemumonitorjsontest.c @@ -2982,8 +2982,8 @@ testQemuMonitorJSONTransaction(const void *opaque) !(mergebitmaps = virJSONValueNewArray())) return -1; - if (virJSONValueArrayAppendString(mergebitmaps, "mergemap1") < 0 || - virJSONValueArrayAppendString(mergebitmaps, "mergemap2") < 0) + if (qemuMonitorTransactionBitmapMergeSourceAddBitmap(mergebitmaps, "node1", "bitmap1") < 0 || + qemuMonitorTransactionBitmapMergeSourceAddBitmap(mergebitmaps, "node2", "bitmap2") < 0) return -1; if (qemuMonitorTransactionBitmapAdd(actions, "node1", "bitmap1", true, true) < 0 || -- 2.21.0

On Fri, Oct 18, 2019 at 06:11:10PM +0200, Peter Krempa wrote:
Introduce qemuMonitorTransactionBitmapMergeSourceAddBitmap which adds the appropriate entry into a virJSONValue array to be used with qemuMonitorTransactionBitmapMerge. Bitmap merging supports two possible formats and this new helper implements the more universal one specifying also the source node name.
In addition use the new helper in the testQemuMonitorJSONTransaction test.
Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_monitor.c | 9 +++++++++ src/qemu/qemu_monitor.h | 4 ++++ src/qemu/qemu_monitor_json.c | 21 +++++++++++++++++++++ src/qemu/qemu_monitor_json.h | 5 +++++ tests/qemumonitorjsontest.c | 4 ++-- 5 files changed, 41 insertions(+), 2 deletions(-)
Reviewed-by: Ján Tomko <jtomko@redhat.com> Jano

Use the new helper in qemuCheckpointDiscard rather than constructing the array manually. Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_checkpoint.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qemu/qemu_checkpoint.c b/src/qemu/qemu_checkpoint.c index 6a272f9dab..937d9d6b3e 100644 --- a/src/qemu/qemu_checkpoint.c +++ b/src/qemu/qemu_checkpoint.c @@ -173,7 +173,7 @@ qemuCheckpointDiscard(virQEMUDriverPtr driver, if (!(arr = virJSONValueNewArray())) return -1; - if (virJSONValueArrayAppendString(arr, disk->bitmap) < 0) + if (qemuMonitorTransactionBitmapMergeSourceAddBitmap(arr, node, disk->bitmap) < 0) return -1; if (chk == virDomainCheckpointGetCurrent(vm->checkpoints)) { -- 2.21.0

On Fri, Oct 18, 2019 at 06:11:11PM +0200, Peter Krempa wrote:
Use the new helper in qemuCheckpointDiscard rather than constructing the array manually.
Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_checkpoint.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/qemu/qemu_checkpoint.c b/src/qemu/qemu_checkpoint.c index 6a272f9dab..937d9d6b3e 100644 --- a/src/qemu/qemu_checkpoint.c +++ b/src/qemu/qemu_checkpoint.c @@ -173,7 +173,7 @@ qemuCheckpointDiscard(virQEMUDriverPtr driver, if (!(arr = virJSONValueNewArray())) return -1;
- if (virJSONValueArrayAppendString(arr, disk->bitmap) < 0) + if (qemuMonitorTransactionBitmapMergeSourceAddBitmap(arr, node, disk->bitmap) < 0) return -1;
This would look better with less indentation. Reviewed-by: Ján Tomko <jtomko@redhat.com> Jano

Use only one switch case selecting job type and decide what's successful outcome on a case-by-case basis. Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_blockjob.c | 81 ++++++++++++---------------------------- 1 file changed, 23 insertions(+), 58 deletions(-) diff --git a/src/qemu/qemu_blockjob.c b/src/qemu/qemu_blockjob.c index f9b3bdaff4..127a04e840 100644 --- a/src/qemu/qemu_blockjob.c +++ b/src/qemu/qemu_blockjob.c @@ -1254,75 +1254,40 @@ qemuBlockJobEventProcessConcludedTransition(qemuBlockJobDataPtr job, virDomainObjPtr vm, qemuDomainAsyncJob asyncJob) { - switch ((qemuBlockjobState) job->newstate) { - case QEMU_BLOCKJOB_STATE_COMPLETED: - switch ((qemuBlockJobType) job->type) { - case QEMU_BLOCKJOB_TYPE_PULL: + bool success = job->newstate == QEMU_BLOCKJOB_STATE_COMPLETED; + + switch ((qemuBlockJobType) job->type) { + case QEMU_BLOCKJOB_TYPE_PULL: + if (success) qemuBlockJobProcessEventCompletedPull(driver, vm, job, asyncJob); - break; + break; - case QEMU_BLOCKJOB_TYPE_COMMIT: + case QEMU_BLOCKJOB_TYPE_COMMIT: + if (success) qemuBlockJobProcessEventCompletedCommit(driver, vm, job, asyncJob); - break; - - case QEMU_BLOCKJOB_TYPE_ACTIVE_COMMIT: - qemuBlockJobProcessEventCompletedActiveCommit(driver, vm, job, asyncJob); - break; - - case QEMU_BLOCKJOB_TYPE_CREATE: - qemuBlockJobProcessEventConcludedCreate(driver, vm, job, asyncJob); - break; - - case QEMU_BLOCKJOB_TYPE_COPY: - if (job->state == QEMU_BLOCKJOB_STATE_PIVOTING) - qemuBlockJobProcessEventConcludedCopyPivot(driver, vm, job, asyncJob); - else - qemuBlockJobProcessEventConcludedCopyAbort(driver, vm, job, asyncJob); - break; - - case QEMU_BLOCKJOB_TYPE_NONE: - case QEMU_BLOCKJOB_TYPE_INTERNAL: - case QEMU_BLOCKJOB_TYPE_LAST: - default: - break; - } break; - case QEMU_BLOCKJOB_STATE_FAILED: - case QEMU_BLOCKJOB_STATE_CANCELLED: - switch ((qemuBlockJobType) job->type) { - case QEMU_BLOCKJOB_TYPE_PULL: - case QEMU_BLOCKJOB_TYPE_COMMIT: - break; - - case QEMU_BLOCKJOB_TYPE_ACTIVE_COMMIT: + case QEMU_BLOCKJOB_TYPE_ACTIVE_COMMIT: + if (success) + qemuBlockJobProcessEventCompletedActiveCommit(driver, vm, job, asyncJob); + else qemuBlockJobProcessEventFailedActiveCommit(driver, vm, job); - break; + break; - case QEMU_BLOCKJOB_TYPE_CREATE: - qemuBlockJobProcessEventConcludedCreate(driver, vm, job, asyncJob); - break; + case QEMU_BLOCKJOB_TYPE_CREATE: + qemuBlockJobProcessEventConcludedCreate(driver, vm, job, asyncJob); + break; - case QEMU_BLOCKJOB_TYPE_COPY: + case QEMU_BLOCKJOB_TYPE_COPY: + if (job->state == QEMU_BLOCKJOB_STATE_PIVOTING && success) + qemuBlockJobProcessEventConcludedCopyPivot(driver, vm, job, asyncJob); + else qemuBlockJobProcessEventConcludedCopyAbort(driver, vm, job, asyncJob); - break; - - case QEMU_BLOCKJOB_TYPE_NONE: - case QEMU_BLOCKJOB_TYPE_INTERNAL: - case QEMU_BLOCKJOB_TYPE_LAST: - default: - break; - } break; - /* states below are impossible in this handler */ - case QEMU_BLOCKJOB_STATE_READY: - case QEMU_BLOCKJOB_STATE_NEW: - case QEMU_BLOCKJOB_STATE_RUNNING: - case QEMU_BLOCKJOB_STATE_CONCLUDED: - case QEMU_BLOCKJOB_STATE_ABORTING: - case QEMU_BLOCKJOB_STATE_PIVOTING: - case QEMU_BLOCKJOB_STATE_LAST: + case QEMU_BLOCKJOB_TYPE_NONE: + case QEMU_BLOCKJOB_TYPE_INTERNAL: + case QEMU_BLOCKJOB_TYPE_LAST: default: break; } -- 2.21.0

On Fri, Oct 18, 2019 at 06:11:12PM +0200, Peter Krempa wrote:
Use only one switch case selecting job type and decide what's successful outcome on a case-by-case basis.
Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_blockjob.c | 81 ++++++++++++---------------------------- 1 file changed, 23 insertions(+), 58 deletions(-)
Reviewed-by: Ján Tomko <jtomko@redhat.com> Jano

Prepare the function for addition of new members to clean. Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_blockjob.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/qemu/qemu_blockjob.c b/src/qemu/qemu_blockjob.c index 127a04e840..e12dcde729 100644 --- a/src/qemu/qemu_blockjob.c +++ b/src/qemu/qemu_blockjob.c @@ -82,8 +82,8 @@ qemuBlockJobDataDispose(void *obj) if (job->type == QEMU_BLOCKJOB_TYPE_CREATE) virObjectUnref(job->data.create.src); - VIR_FREE(job->name); - VIR_FREE(job->errmsg); + g_free(job->name); + g_free(job->errmsg); } -- 2.21.0

On Fri, Oct 18, 2019 at 06:11:13PM +0200, Peter Krempa wrote:
Prepare the function for addition of new members to clean.
Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_blockjob.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
Reviewed-by: Ján Tomko <jtomko@redhat.com> Jano

From: Eric Blake <eblake@redhat.com> Prepare for new backup APIs by describing the XML that will represent a backup. The XML resembles snapshots and checkpoints in being able to select actions for a set of disks, but has other differences. It can support both push model (the hypervisor does the backup directly into the destination file) and pull model (the hypervisor exposes an access port for a third party to grab what is necessary). Add testsuite coverage for some minimal uses of the XML. The <disk> element within <domainbackup> tries to model the same elements as a <disk> under <domain>, but sharing the RNG grammar proved to be hairy. That is in part because while <domain> use <source> to describe a host resource in use by the guest, a backup job is using a host resource that is not visible to the guest: a push backup action is instead describing a <target> (which ultimately could be a remote network resource, but for simplicity the RNG just validates a local file for now), and a pull backup action is instead describing a temporary local file <scratch> (which probably should not be a remote resource). A future refactoring may thus introduce some way to parameterize RNG to accept <disk type='FOO'>...</disk> so that the name of the subelement can be <source> for domain, or <target> or <scratch> as needed for backups. Future patches may improve this area of code. Signed-off-by: Eric Blake <eblake@redhat.com> --- docs/docs.html.in | 3 +- docs/format.html.in | 1 + docs/formatbackup.html.in | 168 ++++++++++++++ docs/formatcheckpoint.html.in | 12 +- docs/index.html.in | 3 +- docs/schemas/domainbackup.rng | 219 +++++++++++++++++++ libvirt.spec.in | 1 + mingw-libvirt.spec.in | 2 + tests/Makefile.am | 2 + tests/domainbackupxml2xmlin/backup-pull.xml | 9 + tests/domainbackupxml2xmlin/backup-push.xml | 9 + tests/domainbackupxml2xmlin/empty.xml | 1 + tests/domainbackupxml2xmlout/backup-pull.xml | 9 + tests/domainbackupxml2xmlout/backup-push.xml | 9 + tests/domainbackupxml2xmlout/empty.xml | 7 + tests/virschematest.c | 2 + 16 files changed, 449 insertions(+), 8 deletions(-) create mode 100644 docs/formatbackup.html.in create mode 100644 docs/schemas/domainbackup.rng create mode 100644 tests/domainbackupxml2xmlin/backup-pull.xml create mode 100644 tests/domainbackupxml2xmlin/backup-push.xml create mode 100644 tests/domainbackupxml2xmlin/empty.xml create mode 100644 tests/domainbackupxml2xmlout/backup-pull.xml create mode 100644 tests/domainbackupxml2xmlout/backup-push.xml create mode 100644 tests/domainbackupxml2xmlout/empty.xml diff --git a/docs/docs.html.in b/docs/docs.html.in index 6cf09f51bc..6eb85b8d25 100644 --- a/docs/docs.html.in +++ b/docs/docs.html.in @@ -82,7 +82,8 @@ <a href="formatnode.html">node devices</a>, <a href="formatsecret.html">secrets</a>, <a href="formatsnapshot.html">snapshots</a>, - <a href="formatcheckpoint.html">checkpoints</a></dd> + <a href="formatcheckpoint.html">checkpoints</a>, + <a href="formatbackup.html">backup jobs</a></dd> <dt><a href="uri.html">URI format</a></dt> <dd>The URI formats used for connecting to libvirt</dd> diff --git a/docs/format.html.in b/docs/format.html.in index 3be2237663..d013528fe0 100644 --- a/docs/format.html.in +++ b/docs/format.html.in @@ -27,6 +27,7 @@ <li><a href="formatsecret.html">Secrets</a></li> <li><a href="formatsnapshot.html">Snapshots</a></li> <li><a href="formatcheckpoint.html">Checkpoints</a></li> + <li><a href="formatbackup.html">Backup jobs</a></li> </ul> <h2>Command line validation</h2> diff --git a/docs/formatbackup.html.in b/docs/formatbackup.html.in new file mode 100644 index 0000000000..272d9cf6ab --- /dev/null +++ b/docs/formatbackup.html.in @@ -0,0 +1,168 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html> +<html xmlns="http://www.w3.org/1999/xhtml"> + <body> + <h1>Backup XML format</h1> + + <ul id="toc"></ul> + + <h2><a id="BackupAttributes">Backup XML</a></h2> + + <p> + Creating a backup, whether full or incremental, is done + via <code>virDomainBackupBegin()</code>, which takes an XML + description of the actions to perform, as well as an optional + second XML document <a href="formatcheckpoint.html">describing a + checkpoint</a> to create at the same point in time. See + also <a href="domainstatecapture.html">a comparison</a> between + the various state capture APIs. + </p> + <p> + There are two general modes for backups: a push mode (where the + hypervisor writes out the data to the destination file, which + may be local or remote), and a pull mode (where the hypervisor + creates an NBD server that a third-party client can then read as + needed, and which requires the use of temporary storage, + typically local, until the backup is complete). + </p> + <p> + The instructions for beginning a backup job are provided as + attributes and elements of the + top-level <code>domainbackup</code> element. This element + includes an optional attribute <code>mode</code> which can be + either "push" or "pull" (default + push). <code>virDomainBackupGetXMLDesc()</code> can be used to + see the actual values selected for elements omitted during + creation (for example, learning which port the NBD server is + using in the pull model or what file names libvirt generated + when none were supplied). The following child elements and attributes + are supported: + </p> + <dl> + <dt>attribute <code>id</code></dt> + <dd>Ignored on input, this element is the job id of the backup + operation returned on success + from <code>virDomainBackupBegin()</code>, and is used for + selecting which backup operation to target + during <code>virDomainBackupGetXMLDesc()</code> + and <code>virDomainBackupEnd()</code>. </dd> + <dt><code>incremental</code></dt> + <dd>An optional element giving the name of an existing + checkpoint of the domain, which will be used to make this + backup an incremental one. In the push model, only changes + since the named checkpoint are written to the destination. In + the pull model, the NBD server uses the + NBD_OPT_SET_META_CONTEXT extension to advertise to the client + which portions of the export contain changes since the named + checkpoint. If omitted, a full backup is performed. + </dd> + <dt><code>server</code></dt> + <dd>Present only for a pull mode backup. Contains the same + attributes as + the <a href="formatdomain.html#elementsDisks"><code>protocol</code> + element of a disk</a> attached via NBD in the domain (such as + transport, socket, name, port, or tls), necessary to set up an + NBD server that exposes the content of each disk at the time + the backup is started. + </dd> + <dt><code>disks</code></dt> + <dd>An optional listing of instructions for disks participating + in the backup (if omitted, all disks participate and libvirt + attempts to generate filenames by appending the current + timestamp as a suffix). If the entire element was omitted on + input, then all disks participate in the backup, otherwise, + only the disks explicitly listed which do not also + use <code>backup='no'</code> will participate. On output, this + is the state of each of the domain's disk in relation to the + backup operation. + <dl> + <dt><code>disk</code></dt> + <dd>This sub-element describes the backup properties of a + specific disk, with the following attributes and child + elements: + <dl> + <dt><code>name</code></dt> + <dd>A mandatory attribute which must match + the <code><target dev='name'/></code> + of one of + the <a href="formatdomain.html#elementsDisks">disk + devices</a> specified for the domain at the time of + the checkpoint.</dd> + <dt><code>backup</code></dt> + <dd>Setting this attribute to <code>yes</code>(default) specifies + that the disk should take part in the backup and using + <code>no</code> excludes the disk from the backup.</dd> + <dt><code>type</code></dt> + <dd>A mandatory attribute to describe the type of the + disk, except when <code>backup='no'</code> is + used. Valid values include <code>file</code>, + <code>block</code>, or <code>network</code>. + Similar to a disk declaration for a domain, the choice of type + controls what additional sub-elements are needed to describe + the destination (such as <code>protocol</code> for a + network destination).</dd> + <dt><code>target</code></dt> + <dd>Valid only for push mode backups, this is the + primary sub-element that describes the file name of + the backup destination, similar to + the <code>source</code> sub-element of a domain + disk. An optional sub-element <code>driver</code> can + also be used, with an attribute <code>type</code> to + specify a destination format different from + qcow2. </dd> + <dt><code>scratch</code></dt> + <dd>Valid only for pull mode backups, this is the + primary sub-element that describes the file name of + the local scratch file to be used in facilitating the + backup, and is similar to the <code>source</code> + sub-element of a domain disk.</dd> + </dl> + </dd> + </dl> + </dd> + </dl> + + <h2><a id="example">Examples</a></h2> + + <p>Use <code>virDomainBackupBegin()</code> to perform a full + backup using push mode. The example lets libvirt pick the + destination and format for 'vda', fully specifies that we want a + raw backup of 'vdb', and omits 'vdc' from the operation. + </p> + <pre> +<domainbackup> + <disks/> + <disk name='vda' backup='yes'/> + <disk name='vdb' type='file'> + <target file='/path/to/vdb.backup'/> + <driver type='raw'/> + </disk> + <disk name='vdc' backup='no'/> + </disks/> +</domainbackup> + </pre> + + <p>If the previous full backup also passed a parameter describing + <a href="formatcheckpoint.html">checkpoint XML</a> that resulted + in a checkpoint named <code>1525889631</code>, we can make + another call to <code>virDomainBackupBegin()</code> to perform + an incremental backup of just the data changed since that + checkpoint, this time using the following XML to start a pull + model export of the 'vda' and 'vdb' disks, where a third-party + NBD client connecting to '/path/to/server' completes the backup + (omitting 'vdc' from the explicit list has the same effect as + the backup='no' from the previous example): + </p> + <pre> +<domainbackup mode="pull"> + <incremental>1525889631</incremental> + <server transport="unix" socket="/path/to/server"/> + <disks/> + <disk name='vda' backup='yes' type='file'> + <scratch file='/path/to/file1.scratch'/> + </disk> + </disks/> +</domainbackup> + </pre> + </body> +</html> diff --git a/docs/formatcheckpoint.html.in b/docs/formatcheckpoint.html.in index 044bbfe4b0..ee56194523 100644 --- a/docs/formatcheckpoint.html.in +++ b/docs/formatcheckpoint.html.in @@ -28,12 +28,12 @@ first checkpoint and the second backup operation), it is possible to do an offline reconstruction of the state of the disk at the time of the second backup without having to copy as - much data as a second full backup would require. Future API - additions will make it possible to create checkpoints in - conjunction with a backup - via <code>virDomainBackupBegin()</code> or with an external - snapshot via <code>virDomainSnapshotCreateXML2</code>; but for - now, libvirt exposes enough support to create disk checkpoints + much data as a second full backup would require. Most disk + checkpoints are created in conjunction with a backup + via <code>virDomainBackupBegin()</code>, although a future API + addition of <code>virDomainSnapshotCreateXML2()</code> will also + make this possible when creating external snapshots; however, + libvirt also exposes enough support to create disk checkpoints independently from a backup operation via <code>virDomainCheckpointCreateXML()</code> <span class="since">since 5.6.0</span>. Likewise, the creation of checkpoints when diff --git a/docs/index.html.in b/docs/index.html.in index 7d0ab650e3..26e8406917 100644 --- a/docs/index.html.in +++ b/docs/index.html.in @@ -59,7 +59,8 @@ <a href="formatnode.html">node devices</a>, <a href="formatsecret.html">secrets</a>, <a href="formatsnapshot.html">snapshots</a>, - <a href="formatcheckpoint.html">checkpoints</a></dd> + <a href="formatcheckpoint.html">checkpoints</a>, + <a href="formatbackup.html">backup jobs</a></dd> <dt><a href="http://wiki.libvirt.org">Wiki</a></dt> <dd>Read further community contributed content</dd> </dl> diff --git a/docs/schemas/domainbackup.rng b/docs/schemas/domainbackup.rng new file mode 100644 index 0000000000..92327e7077 --- /dev/null +++ b/docs/schemas/domainbackup.rng @@ -0,0 +1,219 @@ +<?xml version="1.0"?> +<!-- A Relax NG schema for the libvirt domain backup properties XML format --> +<grammar xmlns="http://relaxng.org/ns/structure/1.0"> + <start> + <ref name='domainbackup'/> + </start> + + <include href='domaincommon.rng'/> + + <define name='domainbackup'> + <element name='domainbackup'> + <optional> + <attribute name='id'> + <ref name="unsignedInt"/> + </attribute> + </optional> + <interleave> + <optional> + <element name='incremental'> + <text/> + </element> + </optional> + <choice> + <group> + <optional> + <attribute name='mode'> + <value>push</value> + </attribute> + </optional> + <ref name='backupDisksPush'/> + </group> + <group> + <attribute name='mode'> + <value>pull</value> + </attribute> + <interleave> + <element name='server'> + <choice> + <group> + <optional> + <attribute name='transport'> + <value>tcp</value> + </attribute> + </optional> + <attribute name='name'> + <choice> + <ref name='dnsName'/> + <ref name='ipAddr'/> + </choice> + </attribute> + <optional> + <attribute name='port'> + <ref name='unsignedInt'/> + </attribute> + </optional> + <!-- add tls? --> + </group> + <group> + <attribute name='transport'> + <value>unix</value> + </attribute> + <attribute name='socket'> + <ref name='absFilePath'/> + </attribute> + </group> + </choice> + </element> + <ref name='backupDisksPull'/> + </interleave> + </group> + </choice> + </interleave> + </element> + </define> + + <define name='backupPushDriver'> + <optional> + <element name='driver'> + <attribute name='type'> + <ref name='storageFormat'/> + </attribute> + </element> + </optional> + </define> + + <define name='backupAttr'> + <!-- valid values of <disk backup='XXX'> other than 'no' --> + <optional> + <attribute name='backup'> + <choice> + <value>begin</value> + <value>inprogress</value> + <value>ready</value> + </choice> + </attribute> + </optional> + </define> + + <define name='backupDisksPush'> + <optional> + <element name='disks'> + <oneOrMore> + <element name='disk'> + <attribute name='name'> + <choice> + <ref name='diskTarget'/> + <ref name='absFilePath'/> + </choice> + </attribute> + <optional> + <attribute name='shallow'> + <value>on</value> + </attribute> + </optional> + <choice> + <group> + <attribute name='backup'> + <value>no</value> + </attribute> + </group> + <!-- FIXME allow push to a network location, perhaps by + refactoring 'diskSource' to take element name by a + per-grammar ref --> + <group> + <ref name='backupAttr'/> + <optional> + <attribute name='type'> + <value>file</value> + </attribute> + </optional> + <interleave> + <optional> + <element name='target'> + <attribute name='file'> + <ref name='absFilePath'/> + </attribute> + </element> + </optional> + <ref name='backupPushDriver'/> + </interleave> + </group> + <group> + <ref name='backupAttr'/> + <attribute name='type'> + <value>block</value> + </attribute> + <interleave> + <optional> + <element name='target'> + <attribute name='dev'> + <ref name='absFilePath'/> + </attribute> + </element> + </optional> + <ref name='backupPushDriver'/> + </interleave> + </group> + <!-- + <group> + <ref name='backupAttr'/> + <attribute name='type'> + <value>network</value> + </attribute> + ... + </group> + --> + </choice> + </element> + </oneOrMore> + </element> + </optional> + </define> + + <define name='backupDisksPull'> + <optional> + <element name='disks'> + <oneOrMore> + <element name='disk'> + <attribute name='name'> + <choice> + <ref name='diskTarget'/> + <ref name='absFilePath'/> + </choice> + </attribute> + <choice> + <group> + <optional> + <attribute name='type'> + <value>file</value> + </attribute> + </optional> + <optional> + <element name='scratch'> + <attribute name='file'> + <ref name='absFilePath'/> + </attribute> + </element> + </optional> + </group> + <group> + <attribute name='type'> + <value>block</value> + </attribute> + <optional> + <element name='scratch'> + <attribute name='dev'> + <ref name='absFilePath'/> + </attribute> + </element> + </optional> + </group> + </choice> + </element> + </oneOrMore> + </element> + </optional> + </define> + +</grammar> diff --git a/libvirt.spec.in b/libvirt.spec.in index dcad08cb5f..0565992907 100644 --- a/libvirt.spec.in +++ b/libvirt.spec.in @@ -1900,6 +1900,7 @@ exit 0 %{_datadir}/libvirt/schemas/capability.rng %{_datadir}/libvirt/schemas/cputypes.rng %{_datadir}/libvirt/schemas/domain.rng +%{_datadir}/libvirt/schemas/domainbackup.rng %{_datadir}/libvirt/schemas/domaincaps.rng %{_datadir}/libvirt/schemas/domaincheckpoint.rng %{_datadir}/libvirt/schemas/domaincommon.rng diff --git a/mingw-libvirt.spec.in b/mingw-libvirt.spec.in index c29f3eeed2..453570fc3c 100644 --- a/mingw-libvirt.spec.in +++ b/mingw-libvirt.spec.in @@ -233,6 +233,7 @@ rm -rf $RPM_BUILD_ROOT%{mingw64_libexecdir}/libvirt-guests.sh %{mingw32_datadir}/libvirt/schemas/capability.rng %{mingw32_datadir}/libvirt/schemas/cputypes.rng %{mingw32_datadir}/libvirt/schemas/domain.rng +%{mingw32_datadir}/libvirt/schemas/domainbackup.rng %{mingw32_datadir}/libvirt/schemas/domaincaps.rng %{mingw32_datadir}/libvirt/schemas/domaincheckpoint.rng %{mingw32_datadir}/libvirt/schemas/domaincommon.rng @@ -324,6 +325,7 @@ rm -rf $RPM_BUILD_ROOT%{mingw64_libexecdir}/libvirt-guests.sh %{mingw64_datadir}/libvirt/schemas/capability.rng %{mingw64_datadir}/libvirt/schemas/cputypes.rng %{mingw64_datadir}/libvirt/schemas/domain.rng +%{mingw64_datadir}/libvirt/schemas/domainbackup.rng %{mingw64_datadir}/libvirt/schemas/domaincaps.rng %{mingw64_datadir}/libvirt/schemas/domaincheckpoint.rng %{mingw64_datadir}/libvirt/schemas/domaincommon.rng diff --git a/tests/Makefile.am b/tests/Makefile.am index a9acd88670..d7459de9b1 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -89,6 +89,8 @@ EXTRA_DIST = \ capabilityschemadata \ commanddata \ cputestdata \ + domainbackupxml2xmlin \ + domainbackupxml2xmlout \ domaincapsschemadata \ domainconfdata \ domainschemadata \ diff --git a/tests/domainbackupxml2xmlin/backup-pull.xml b/tests/domainbackupxml2xmlin/backup-pull.xml new file mode 100644 index 0000000000..2ce5cd6711 --- /dev/null +++ b/tests/domainbackupxml2xmlin/backup-pull.xml @@ -0,0 +1,9 @@ +<domainbackup mode="pull"> + <incremental>1525889631</incremental> + <server transport='tcp' name='localhost' port='10809'/> + <disks> + <disk name='vda' type='file'> + <scratch file='/path/to/file'/> + </disk> + </disks> +</domainbackup> diff --git a/tests/domainbackupxml2xmlin/backup-push.xml b/tests/domainbackupxml2xmlin/backup-push.xml new file mode 100644 index 0000000000..1b7d3061fd --- /dev/null +++ b/tests/domainbackupxml2xmlin/backup-push.xml @@ -0,0 +1,9 @@ +<domainbackup mode="push"> + <incremental>1525889631</incremental> + <disks> + <disk name='vda' type='file'> + <driver type='raw'/> + <target file='/path/to/file'/> + </disk> + </disks> +</domainbackup> diff --git a/tests/domainbackupxml2xmlin/empty.xml b/tests/domainbackupxml2xmlin/empty.xml new file mode 100644 index 0000000000..7ed511f97b --- /dev/null +++ b/tests/domainbackupxml2xmlin/empty.xml @@ -0,0 +1 @@ +<domainbackup/> diff --git a/tests/domainbackupxml2xmlout/backup-pull.xml b/tests/domainbackupxml2xmlout/backup-pull.xml new file mode 100644 index 0000000000..2ce5cd6711 --- /dev/null +++ b/tests/domainbackupxml2xmlout/backup-pull.xml @@ -0,0 +1,9 @@ +<domainbackup mode="pull"> + <incremental>1525889631</incremental> + <server transport='tcp' name='localhost' port='10809'/> + <disks> + <disk name='vda' type='file'> + <scratch file='/path/to/file'/> + </disk> + </disks> +</domainbackup> diff --git a/tests/domainbackupxml2xmlout/backup-push.xml b/tests/domainbackupxml2xmlout/backup-push.xml new file mode 100644 index 0000000000..1b7d3061fd --- /dev/null +++ b/tests/domainbackupxml2xmlout/backup-push.xml @@ -0,0 +1,9 @@ +<domainbackup mode="push"> + <incremental>1525889631</incremental> + <disks> + <disk name='vda' type='file'> + <driver type='raw'/> + <target file='/path/to/file'/> + </disk> + </disks> +</domainbackup> diff --git a/tests/domainbackupxml2xmlout/empty.xml b/tests/domainbackupxml2xmlout/empty.xml new file mode 100644 index 0000000000..13600fbb1c --- /dev/null +++ b/tests/domainbackupxml2xmlout/empty.xml @@ -0,0 +1,7 @@ +<domainbackup mode="push"> + <disks> + <disk name="vda" type="file"> + <target file="/path/to/file1.copy"/> + </disk> + </disks> +</domainbackup> diff --git a/tests/virschematest.c b/tests/virschematest.c index dabbc02163..330c42b010 100644 --- a/tests/virschematest.c +++ b/tests/virschematest.c @@ -221,6 +221,8 @@ mymain(void) "lxcxml2xmloutdata", "bhyvexml2argvdata", "genericxml2xmlindata", "genericxml2xmloutdata", "xlconfigdata", "libxlxml2domconfigdata", "qemuhotplugtestdomains"); + DO_TEST_DIR("domainbackup.rng", "domainbackupxml2xmlin", + "domainbackupxml2xmlout"); DO_TEST_DIR("domaincaps.rng", "domaincapsschemadata"); DO_TEST_DIR("domaincheckpoint.rng", "qemudomaincheckpointxml2xmlin", "qemudomaincheckpointxml2xmlout"); -- 2.21.0

From: Eric Blake <eblake@redhat.com> Introduce a few new public APIs related to incremental backups. This builds on the previous notion of a checkpoint (without an existing checkpoint, the new API is a full backup, differing from virDomainBlockCopy in the point of time chosen and in operation on multiple disks at once); and also allows creation of a new checkpoint at the same time as starting the backup (after all, an incremental backup is only useful if it covers the state since the previous backup). A backup job also affects filtering a listing of domains, as well as adding event reporting for signaling when a push model backup completes (where the hypervisor creates the backup); note that the pull model does not have an event (starting the backup lets a third party access the data, and only the third party knows when it is finished). Since multiple backup jobs can be run in parallel in the future (well, qemu doesn't support it yet, but we don't want to preclude the idea), virDomainBackupBegin() returns a positive job id, and the id is also visible in the backup XML. But until a future libvirt release adds a bunch of APIs related to parallel job management where job ids will actually matter, the documentation is also clear that job id 0 means the 'currently running backup job' (provided one exists), for use in virDomainBackupGetXMLDesc() and virDomainBackupEnd(). The full list of new APIs: virDomainBackupBegin; virDomainBackupEnd; virDomainBackupGetXMLDesc; Signed-off-by: Eric Blake <eblake@redhat.com> Reviewed-by: Daniel P. Berrangé <berrange@redhat.com> --- include/libvirt/libvirt-domain.h | 26 ++++- src/driver-hypervisor.h | 20 ++++ src/libvirt-domain-checkpoint.c | 7 +- src/libvirt-domain.c | 191 +++++++++++++++++++++++++++++++ src/libvirt_public.syms | 8 ++ tools/virsh-domain.c | 4 +- 6 files changed, 252 insertions(+), 4 deletions(-) diff --git a/include/libvirt/libvirt-domain.h b/include/libvirt/libvirt-domain.h index 22277b0a84..2d9f69f7d4 100644 --- a/include/libvirt/libvirt-domain.h +++ b/include/libvirt/libvirt-domain.h @@ -3267,6 +3267,7 @@ typedef enum { VIR_DOMAIN_JOB_OPERATION_SNAPSHOT = 6, VIR_DOMAIN_JOB_OPERATION_SNAPSHOT_REVERT = 7, VIR_DOMAIN_JOB_OPERATION_DUMP = 8, + VIR_DOMAIN_JOB_OPERATION_BACKUP = 9, # ifdef VIR_ENUM_SENTINELS VIR_DOMAIN_JOB_OPERATION_LAST @@ -3282,6 +3283,14 @@ typedef enum { */ # define VIR_DOMAIN_JOB_OPERATION "operation" +/** + * VIR_DOMAIN_JOB_ID: + * + * virDomainGetJobStats field: the id of the job (so far, only for jobs + * started by virDomainBackupBegin()), as VIR_TYPED_PARAM_INT. + */ +# define VIR_DOMAIN_JOB_ID "id" + /** * VIR_DOMAIN_JOB_TIME_ELAPSED: * @@ -4106,7 +4115,8 @@ typedef void (*virConnectDomainEventMigrationIterationCallback)(virConnectPtr co * @nparams: size of the params array * @opaque: application specific data * - * This callback occurs when a job (such as migration) running on the domain + * This callback occurs when a job (such as migration or push-model + * virDomainBackupBegin()) running on the domain * is completed. The params array will contain statistics of the just completed * job as virDomainGetJobStats would return. The callback must not free @params * (the array will be freed once the callback finishes). @@ -4916,4 +4926,18 @@ int virDomainGetGuestInfo(virDomainPtr domain, int *nparams, unsigned int flags); + +int virDomainBackupBegin(virDomainPtr domain, + const char *backupXML, + const char *checkpointXML, + unsigned int flags); + +char *virDomainBackupGetXMLDesc(virDomainPtr domain, + int id, + unsigned int flags); + +int virDomainBackupEnd(virDomainPtr domain, + int id, + unsigned int flags); + #endif /* LIBVIRT_DOMAIN_H */ diff --git a/src/driver-hypervisor.h b/src/driver-hypervisor.h index 015b2cd01c..cf69cf528d 100644 --- a/src/driver-hypervisor.h +++ b/src/driver-hypervisor.h @@ -1372,6 +1372,23 @@ typedef int int *nparams, unsigned int flags); + +typedef int +(*virDrvDomainBackupBegin)(virDomainPtr domain, + const char *backupXML, + const char *checkpointXML, + unsigned int flags); + +typedef char * +(*virDrvDomainBackupGetXMLDesc)(virDomainPtr domain, + int id, + unsigned int flags); + +typedef int +(*virDrvDomainBackupEnd)(virDomainPtr domain, + int id, + unsigned int flags); + typedef struct _virHypervisorDriver virHypervisorDriver; typedef virHypervisorDriver *virHypervisorDriverPtr; @@ -1632,4 +1649,7 @@ struct _virHypervisorDriver { virDrvDomainCheckpointGetParent domainCheckpointGetParent; virDrvDomainCheckpointDelete domainCheckpointDelete; virDrvDomainGetGuestInfo domainGetGuestInfo; + virDrvDomainBackupBegin domainBackupBegin; + virDrvDomainBackupGetXMLDesc domainBackupGetXMLDesc; + virDrvDomainBackupEnd domainBackupEnd; }; diff --git a/src/libvirt-domain-checkpoint.c b/src/libvirt-domain-checkpoint.c index fa391f8a06..432c2d5a52 100644 --- a/src/libvirt-domain-checkpoint.c +++ b/src/libvirt-domain-checkpoint.c @@ -102,8 +102,11 @@ virDomainCheckpointGetConnect(virDomainCheckpointPtr checkpoint) * @flags: bitwise-OR of supported virDomainCheckpointCreateFlags * * Create a new checkpoint using @xmlDesc, with a top-level - * <domaincheckpoint> element, on a running @domain. Note that @xmlDesc - * must validate against the <domaincheckpoint> XML schema. + * <domaincheckpoint> element, on a running @domain. Note that + * @xmlDesc must validate against the <domaincheckpoint> XML schema. + * Typically, it is more common to create a new checkpoint as part of + * kicking off a backup job with virDomainBackupBegin(); however, it + * is also possible to start a checkpoint without a backup. * * See <a href="formatcheckpoint.html#CheckpointAttributes">Checkpoint XML</a> * for more details on @xmlDesc. In particular, some hypervisors may require diff --git a/src/libvirt-domain.c b/src/libvirt-domain.c index dcab179e6e..7564c9eee1 100644 --- a/src/libvirt-domain.c +++ b/src/libvirt-domain.c @@ -12497,3 +12497,194 @@ int virDomainGetLaunchSecurityInfo(virDomainPtr domain, virDispatchError(domain->conn); return -1; } + + +/** + * virDomainBackupBegin: + * @domain: a domain object + * @backupXML: description of the requested backup + * @checkpointXML: description of a checkpoint to create or NULL + * @flags: unused; callers must pass 0 + * + * Start a point-in-time backup job for the specified disks of a + * running domain. + * + * A backup job is mutually exclusive with domain migration + * (particularly when the job sets up an NBD export, since it is not + * possible to tell any NBD clients about a server migrating between + * hosts). For now, backup jobs are also mutually exclusive with any + * other block job on the same device, although this restriction may + * be lifted in a future release. Progress of the backup job can be + * tracked via virDomainGetJobStats(). The job remains active until a + * subsequent call to virDomainBackupEnd(), even if it no longer has + * anything to copy. + * + * There are two fundamental backup approaches. The first, called a + * push model, instructs the hypervisor to copy the state of the guest + * disk to the designated storage destination (which may be on the + * local file system or a network device). In this mode, the + * hypervisor writes the content of the guest disk to the destination, + * then emits VIR_DOMAIN_EVENT_ID_JOB_COMPLETED when the backup is + * either complete or failed (the backup image is invalid if the job + * fails or virDomainBackupEnd() is used prior to the event being + * emitted). + * + * The second, called a pull model, instructs the hypervisor to expose + * the state of the guest disk over an NBD export. A third-party + * client can then connect to this export and read whichever portions + * of the disk it desires. In this mode, there is no event; libvirt + * has to be informed via virDomainBackupEnd() when the third-party + * NBD client is done and the backup resources can be released. + * + * The @backupXML parameter contains details about the backup in the top-level + * element <domainbackup> , including which backup mode to use, whether the + * backup is incremental from a previous checkpoint, which disks + * participate in the backup, the destination for a push model backup, + * and the temporary storage and NBD server details for a pull model + * backup. + * + * virDomainBackupGetXMLDesc() can be called to learn actual + * values selected. For more information, see + * formatcheckpoint.html#BackupAttributes. + * + * The @checkpointXML parameter is optional; if non-NULL, then libvirt + * behaves as if virDomainCheckpointCreateXML() were called to create + * a checkpoint atomically covering the same point in time as the + * backup. + * The creation of a new checkpoint allows for future incremental backups. + * Note that some hypervisors may require a particular disk format, such as + * qcow2, in order to take advantage of checkpoints, while allowing arbitrary + * formats if checkpoints are not involved. + * + * Returns a non-negative job id on success or negative on failure. + * This id is then passed to virDomainBackupGetXMLDesc() and + * virDomainBackupEnd(). + */ +int +virDomainBackupBegin(virDomainPtr domain, + const char *backupXML, + const char *checkpointXML, + unsigned int flags) +{ + virConnectPtr conn; + + VIR_DOMAIN_DEBUG(domain, "backupXML=%s, checkpointXML=%s, flags=0x%x", + NULLSTR(backupXML), NULLSTR(checkpointXML), flags); + + virResetLastError(); + + virCheckDomainReturn(domain, -1); + conn = domain->conn; + + virCheckNonNullArgGoto(backupXML, error); + virCheckReadOnlyGoto(conn->flags, error); + + if (conn->driver->domainBackupBegin) { + int ret; + ret = conn->driver->domainBackupBegin(domain, backupXML, checkpointXML, + flags); + if (ret < 0) + goto error; + return ret; + } + + virReportUnsupportedError(); + error: + virDispatchError(conn); + return -1; +} + + +/** + * virDomainBackupGetXMLDesc: + * @domain: a domain object + * @id: the id of an active backup job + * @flags: extra flags; not used yet, so callers should always pass 0 + * + * In some cases, a user can start a backup job without supplying all + * details and rely on libvirt to fill in the rest (for example, + * selecting the port used for an NBD export). This API can then be + * used to learn what default values were chosen. + * + * @id can either be the return value of a previous virDomainBackupBegin(). + * + * Returns a NUL-terminated UTF-8 encoded XML instance or NULL in + * case of error. The caller must free() the returned value. + */ +char * +virDomainBackupGetXMLDesc(virDomainPtr domain, + int id, + unsigned int flags) +{ + virConnectPtr conn; + + VIR_DOMAIN_DEBUG(domain, "id=%d, flags=0x%x", id, flags); + + virResetLastError(); + + virCheckDomainReturn(domain, NULL); + conn = domain->conn; + + virCheckNonNegativeArgGoto(id, error); + + if (conn->driver->domainBackupGetXMLDesc) { + char *ret; + ret = conn->driver->domainBackupGetXMLDesc(domain, id, flags); + if (!ret) + goto error; + return ret; + } + + virReportUnsupportedError(); + error: + virDispatchError(conn); + return NULL; +} + + +/** + * virDomainBackupEnd: + * @domain: a domain object + * @id: the id of an active backup job + * @flags: unused; callers must pass 0 + * + * Conclude a point-in-time backup job of the given domain. + * + * In case of a push model backup the backup is aborted and will be incomplete. + * + * This API is asynchronous thus a successful return does not indicate that the + * job was concluded yet. + * + * Returns 0 if the backup job termination was requested successfully, or -1 on + * failure. + */ +int +virDomainBackupEnd(virDomainPtr domain, + int id, + unsigned int flags) +{ + virConnectPtr conn; + + VIR_DOMAIN_DEBUG(domain, "id=%d, flags=0x%x", id, flags); + + virResetLastError(); + + virCheckDomainReturn(domain, -1); + conn = domain->conn; + + virCheckReadOnlyGoto(conn->flags, error); + virCheckNonNegativeArgGoto(id, error); + + if (conn->driver->domainBackupEnd) { + int ret; + ret = conn->driver->domainBackupEnd(domain, id, flags); + if (ret < 0) + goto error; + return ret; + } + + virReportUnsupportedError(); + error: + virDispatchError(conn); + return -1; +} diff --git a/src/libvirt_public.syms b/src/libvirt_public.syms index 40655fbbf5..bf7ef80741 100644 --- a/src/libvirt_public.syms +++ b/src/libvirt_public.syms @@ -858,7 +858,15 @@ LIBVIRT_5.7.0 { } LIBVIRT_5.6.0; LIBVIRT_5.8.0 { + global: virConnectSetIdentity; } LIBVIRT_5.7.0; +LIBVIRT_5.9.0 { + global: + virDomainBackupBegin; + virDomainBackupEnd; + virDomainBackupGetXMLDesc; +} LIBVIRT_5.8.0; + # .... define new API here using predicted next version number .... diff --git a/tools/virsh-domain.c b/tools/virsh-domain.c index 3ba8451470..5ecc8a229c 100644 --- a/tools/virsh-domain.c +++ b/tools/virsh-domain.c @@ -6080,7 +6080,9 @@ VIR_ENUM_IMPL(virshDomainJobOperation, N_("Outgoing migration"), N_("Snapshot"), N_("Snapshot revert"), - N_("Dump")); + N_("Dump"), + N_("Backup"), +); static const char * virshDomainJobOperationToString(int op) -- 2.21.0

On Fri, Oct 18, 2019 at 06:11:15PM +0200, Peter Krempa wrote:
From: Eric Blake <eblake@redhat.com>
Introduce a few new public APIs related to incremental backups. This builds on the previous notion of a checkpoint (without an existing checkpoint, the new API is a full backup, differing from virDomainBlockCopy in the point of time chosen and in operation on multiple disks at once); and also allows creation of a new checkpoint at the same time as starting the backup (after all, an incremental backup is only useful if it covers the state since the previous backup).
A backup job also affects filtering a listing of domains, as well as adding event reporting for signaling when a push model backup completes (where the hypervisor creates the backup); note that the pull model does not have an event (starting the backup lets a third party access the data, and only the third party knows when it is finished).
Since multiple backup jobs can be run in parallel in the future (well, qemu doesn't support it yet, but we don't want to preclude the idea), virDomainBackupBegin() returns a positive job id, and the id is also visible in the backup XML. But until a future libvirt release adds a bunch of APIs related to parallel job management where job ids will actually matter, the documentation is also clear that job id 0 means the 'currently running backup job' (provided one exists), for use in virDomainBackupGetXMLDesc() and virDomainBackupEnd().
The full list of new APIs: virDomainBackupBegin; virDomainBackupEnd; virDomainBackupGetXMLDesc;
Signed-off-by: Eric Blake <eblake@redhat.com> Reviewed-by: Daniel P. Berrangé <berrange@redhat.com> --- include/libvirt/libvirt-domain.h | 26 ++++- src/driver-hypervisor.h | 20 ++++ src/libvirt-domain-checkpoint.c | 7 +- src/libvirt-domain.c | 191 +++++++++++++++++++++++++++++++ src/libvirt_public.syms | 8 ++ tools/virsh-domain.c | 4 +- 6 files changed, 252 insertions(+), 4 deletions(-)
diff --git a/include/libvirt/libvirt-domain.h b/include/libvirt/libvirt-domain.h index 22277b0a84..2d9f69f7d4 100644 --- a/include/libvirt/libvirt-domain.h +++ b/include/libvirt/libvirt-domain.h @@ -3267,6 +3267,7 @@ typedef enum {
+/** + * VIR_DOMAIN_JOB_ID: + * + * virDomainGetJobStats field: the id of the job (so far, only for jobs + * started by virDomainBackupBegin()), as VIR_TYPED_PARAM_INT. + */ +# define VIR_DOMAIN_JOB_ID "id" + /** * VIR_DOMAIN_JOB_TIME_ELAPSED: * @@ -4106,7 +4115,8 @@ typedef void (*virConnectDomainEventMigrationIterationCallback)(virConnectPtr co * @nparams: size of the params array * @opaque: application specific data * - * This callback occurs when a job (such as migration) running on the domain + * This callback occurs when a job (such as migration or push-model + * virDomainBackupBegin()) running on the domain * is completed. The params array will contain statistics of the just completed * job as virDomainGetJobStats would return. The callback must not free @params * (the array will be freed once the callback finishes). @@ -4916,4 +4926,18 @@ int virDomainGetGuestInfo(virDomainPtr domain, int *nparams, unsigned int flags);
+ +int virDomainBackupBegin(virDomainPtr domain, + const char *backupXML, + const char *checkpointXML, + unsigned int flags); + +char *virDomainBackupGetXMLDesc(virDomainPtr domain, + int id, + unsigned int flags); + +int virDomainBackupEnd(virDomainPtr domain, + int id, + unsigned int flags);
So this is still using a plain integer job ID, which is a concern wrt future extensibility. Earlier in the year I queried whether we should turn the "job" into a fully fledged object, using either a string or a UUID to identify it uniquely. https://www.redhat.com/archives/libvir-list/2019-March/msg01695.html IOW having something like this: typedef struct _virDomainJob virDomainJob; typedef virDomainJob *virDomainJobPtr; void virDomainJobFree(virDomainJobPtr job); virDomainJobLookupByUUID(virDomainPtr job, unsigned char *uuid); int virDomainJobGetType(virDomainJobPtr job); int virDomainJobGetUUID(virDomainJobPtr job, unsigned char *uuid); int virDomainJobGetUUIDString(virDomainJobPtr job, char *uuidstr); virDomainJobPtr virDomainBackupBegin(virDomainPtr domain, const char *backupXML, const char *checkpointXML, unsigned int flags); char *virDomainBackupGetXMLDesc(virDomainPtr domain, virDomainJobPtr job, unsigned int flags); int virDomainBackupEnd(virDomainPtr domain, virDomainJobPtr job, unsigned int flags); Privately we'd define struct _virDomainJob { unsigned char uuid[VIR_UUID_BUFLEN]; int type; }; So we don't do a RPC call for virDomainJobGet{Type,UUID,UUIDString}, and we'd just serialize the uuid over the wire for the virDomainBackup APIs I presume. Currently we have a single domain block job that can be active at any time and it would be desirable to fold that into the new API in some way. We can either create new v2 APIs for existing methods making them return a virDomainJobPtr, or we could reserve a well-known UUID exclusively to refer to the single default domain block job, which the user can grab via virDomainJobLookupByUUID(). Regards, Daniel -- |: https://berrange.com -o- https://www.flickr.com/photos/dberrange :| |: https://libvirt.org -o- https://fstop138.berrange.com :| |: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|

On Thu, Nov 21, 2019 at 18:54:12 +0000, Daniel Berrange wrote:
On Fri, Oct 18, 2019 at 06:11:15PM +0200, Peter Krempa wrote:
From: Eric Blake <eblake@redhat.com>
Introduce a few new public APIs related to incremental backups. This builds on the previous notion of a checkpoint (without an existing checkpoint, the new API is a full backup, differing from virDomainBlockCopy in the point of time chosen and in operation on multiple disks at once); and also allows creation of a new checkpoint at the same time as starting the backup (after all, an incremental backup is only useful if it covers the state since the previous backup).
A backup job also affects filtering a listing of domains, as well as adding event reporting for signaling when a push model backup completes (where the hypervisor creates the backup); note that the pull model does not have an event (starting the backup lets a third party access the data, and only the third party knows when it is finished).
Since multiple backup jobs can be run in parallel in the future (well, qemu doesn't support it yet, but we don't want to preclude the idea), virDomainBackupBegin() returns a positive job id, and the id is also visible in the backup XML. But until a future libvirt release adds a bunch of APIs related to parallel job management where job ids will actually matter, the documentation is also clear that job id 0 means the 'currently running backup job' (provided one exists), for use in virDomainBackupGetXMLDesc() and virDomainBackupEnd().
The full list of new APIs: virDomainBackupBegin; virDomainBackupEnd; virDomainBackupGetXMLDesc;
Signed-off-by: Eric Blake <eblake@redhat.com> Reviewed-by: Daniel P. Berrangé <berrange@redhat.com> --- include/libvirt/libvirt-domain.h | 26 ++++- src/driver-hypervisor.h | 20 ++++ src/libvirt-domain-checkpoint.c | 7 +- src/libvirt-domain.c | 191 +++++++++++++++++++++++++++++++ src/libvirt_public.syms | 8 ++ tools/virsh-domain.c | 4 +- 6 files changed, 252 insertions(+), 4 deletions(-)
diff --git a/include/libvirt/libvirt-domain.h b/include/libvirt/libvirt-domain.h index 22277b0a84..2d9f69f7d4 100644 --- a/include/libvirt/libvirt-domain.h +++ b/include/libvirt/libvirt-domain.h @@ -3267,6 +3267,7 @@ typedef enum {
+/** + * VIR_DOMAIN_JOB_ID: + * + * virDomainGetJobStats field: the id of the job (so far, only for jobs + * started by virDomainBackupBegin()), as VIR_TYPED_PARAM_INT. + */ +# define VIR_DOMAIN_JOB_ID "id" + /** * VIR_DOMAIN_JOB_TIME_ELAPSED: * @@ -4106,7 +4115,8 @@ typedef void (*virConnectDomainEventMigrationIterationCallback)(virConnectPtr co * @nparams: size of the params array * @opaque: application specific data * - * This callback occurs when a job (such as migration) running on the domain + * This callback occurs when a job (such as migration or push-model + * virDomainBackupBegin()) running on the domain * is completed. The params array will contain statistics of the just completed * job as virDomainGetJobStats would return. The callback must not free @params * (the array will be freed once the callback finishes). @@ -4916,4 +4926,18 @@ int virDomainGetGuestInfo(virDomainPtr domain, int *nparams, unsigned int flags);
+ +int virDomainBackupBegin(virDomainPtr domain, + const char *backupXML, + const char *checkpointXML, + unsigned int flags); + +char *virDomainBackupGetXMLDesc(virDomainPtr domain, + int id, + unsigned int flags); + +int virDomainBackupEnd(virDomainPtr domain, + int id, + unsigned int flags);
So this is still using a plain integer job ID, which is a concern wrt future extensibility.
My current plan is to go ahead with API based on this old one but with no support for any parallel jobs. Basically the same thing but 'id' argument removed. This actually fits in with the original documentation which was already ACKed for the virDomainBackupBegin API which said that the backup job uses the domain async job infrastructure. This means that the virDomainAbortJob and virDomainGetJobStats can be used to monitor the blockjob. It also gives us the possibility to query the state of a finised job by passing VIR_DOMAIN_JOB_STATS_COMPLETED to virDomainGetJobStats. We also have an async event for reporting the job state. I'll also consider removing virDomainBackupEnd for this implementation as virDomainAbortJob should be enough. I spoke with oVirt devs who are very keen on getting this API finished and their requirements currently don't require any parallel jobs. In fact they were abusing Eric's design which only ever returned the same job ID so that they would not have to persist it. Given that we'll need to deal with the domain job anyways for the better job infra outlined below I don't think adding these APIs will be too much of a burden in the interim so that we can appease oVirt's desire for this feature and we'll have more time to design the new job interface properly.
Earlier in the year I queried whether we should turn the "job" into a fully fledged object, using either a string or a UUID to identify it uniquely.
https://www.redhat.com/archives/libvir-list/2019-March/msg01695.html
IOW having something like this:
Going forward I want this not only for the backup job but basically for any long running operation. We needed this for a long time but of the two long running job impls we have both are not flexible enough. There is only one 'domain job' (migration/save/etc) and blockjobs are bound to disks.
typedef struct _virDomainJob virDomainJob; typedef virDomainJob *virDomainJobPtr;
I actually started some work on this but didn't get far yet. I used the same name in my case, but I'm partially afraid that virDomainAbortJob which would not be related to these objects will be mistaken for actually working in this case. Unfortunately I don't have any better idea.
void virDomainJobFree(virDomainJobPtr job);
virDomainJobLookupByUUID(virDomainPtr job, unsigned char *uuid);
int virDomainJobGetType(virDomainJobPtr job); int virDomainJobGetUUID(virDomainJobPtr job, unsigned char *uuid); int virDomainJobGetUUIDString(virDomainJobPtr job, char *uuidstr);
virDomainJobPtr virDomainBackupBegin(virDomainPtr domain, const char *backupXML, const char *checkpointXML, unsigned int flags);
I was thinking about a super-universal API so that we don't have to redo all APIs for blockjobs. Something along virDomainJobPtr virDomainJobBegin(virDomainPtr domain, const char *jobxml, unsigned int flags); It would give us the flexibility to add new jobs and arguments for them via XML which is more flexible (e.g. we could easily add a virDomainBlockPull with the 'top' argument which is currently missing but qemu started to support it some time ago). On the other hand that seems a too prone to abuse. As of the above I'm unsure about the tradeofs between flexibility and too much flexibility in this case. But that's probably for a future discussion.
char *virDomainBackupGetXMLDesc(virDomainPtr domain, virDomainJobPtr job, unsigned int flags);
int virDomainBackupEnd(virDomainPtr domain, virDomainJobPtr job, unsigned int flags);
Privately we'd define
struct _virDomainJob { unsigned char uuid[VIR_UUID_BUFLEN]; int type; };
So we don't do a RPC call for virDomainJobGet{Type,UUID,UUIDString}, and we'd just serialize the uuid over the wire for the virDomainBackup APIs I presume.
Currently we have a single domain block job that can be active at any time and it would be desirable to fold that into the new API in some way.
Yes exactly.
We can either create new v2 APIs for existing methods making them return a virDomainJobPtr, or we could reserve a well-known UUID exclusively to refer to the single default domain block job, which the user can grab via virDomainJobLookupByUUID().
Yes I was thinking along the same lines. Given that we'll need to deal with this anyways I don't feel as bad adding the backup job as a domain job for now with all the quirks and also all the infrastructure a domain job already provides and then dealing wit the domain jobs properly later.

On Fri, Nov 22, 2019 at 09:06:31AM +0100, Peter Krempa wrote:
On Thu, Nov 21, 2019 at 18:54:12 +0000, Daniel Berrange wrote:
On Fri, Oct 18, 2019 at 06:11:15PM +0200, Peter Krempa wrote:
From: Eric Blake <eblake@redhat.com>
Introduce a few new public APIs related to incremental backups. This builds on the previous notion of a checkpoint (without an existing checkpoint, the new API is a full backup, differing from virDomainBlockCopy in the point of time chosen and in operation on multiple disks at once); and also allows creation of a new checkpoint at the same time as starting the backup (after all, an incremental backup is only useful if it covers the state since the previous backup).
A backup job also affects filtering a listing of domains, as well as adding event reporting for signaling when a push model backup completes (where the hypervisor creates the backup); note that the pull model does not have an event (starting the backup lets a third party access the data, and only the third party knows when it is finished).
Since multiple backup jobs can be run in parallel in the future (well, qemu doesn't support it yet, but we don't want to preclude the idea), virDomainBackupBegin() returns a positive job id, and the id is also visible in the backup XML. But until a future libvirt release adds a bunch of APIs related to parallel job management where job ids will actually matter, the documentation is also clear that job id 0 means the 'currently running backup job' (provided one exists), for use in virDomainBackupGetXMLDesc() and virDomainBackupEnd().
The full list of new APIs: virDomainBackupBegin; virDomainBackupEnd; virDomainBackupGetXMLDesc;
Signed-off-by: Eric Blake <eblake@redhat.com> Reviewed-by: Daniel P. Berrangé <berrange@redhat.com> --- include/libvirt/libvirt-domain.h | 26 ++++- src/driver-hypervisor.h | 20 ++++ src/libvirt-domain-checkpoint.c | 7 +- src/libvirt-domain.c | 191 +++++++++++++++++++++++++++++++ src/libvirt_public.syms | 8 ++ tools/virsh-domain.c | 4 +- 6 files changed, 252 insertions(+), 4 deletions(-)
diff --git a/include/libvirt/libvirt-domain.h b/include/libvirt/libvirt-domain.h index 22277b0a84..2d9f69f7d4 100644 --- a/include/libvirt/libvirt-domain.h +++ b/include/libvirt/libvirt-domain.h @@ -3267,6 +3267,7 @@ typedef enum {
+/** + * VIR_DOMAIN_JOB_ID: + * + * virDomainGetJobStats field: the id of the job (so far, only for jobs + * started by virDomainBackupBegin()), as VIR_TYPED_PARAM_INT. + */ +# define VIR_DOMAIN_JOB_ID "id" + /** * VIR_DOMAIN_JOB_TIME_ELAPSED: * @@ -4106,7 +4115,8 @@ typedef void (*virConnectDomainEventMigrationIterationCallback)(virConnectPtr co * @nparams: size of the params array * @opaque: application specific data * - * This callback occurs when a job (such as migration) running on the domain + * This callback occurs when a job (such as migration or push-model + * virDomainBackupBegin()) running on the domain * is completed. The params array will contain statistics of the just completed * job as virDomainGetJobStats would return. The callback must not free @params * (the array will be freed once the callback finishes). @@ -4916,4 +4926,18 @@ int virDomainGetGuestInfo(virDomainPtr domain, int *nparams, unsigned int flags);
+ +int virDomainBackupBegin(virDomainPtr domain, + const char *backupXML, + const char *checkpointXML, + unsigned int flags); + +char *virDomainBackupGetXMLDesc(virDomainPtr domain, + int id, + unsigned int flags); + +int virDomainBackupEnd(virDomainPtr domain, + int id, + unsigned int flags);
So this is still using a plain integer job ID, which is a concern wrt future extensibility.
My current plan is to go ahead with API based on this old one but with no support for any parallel jobs. Basically the same thing but 'id' argument removed.
This actually fits in with the original documentation which was already ACKed for the virDomainBackupBegin API which said that the backup job uses the domain async job infrastructure. This means that the virDomainAbortJob and virDomainGetJobStats can be used to monitor the blockjob. It also gives us the possibility to query the state of a finised job by passing VIR_DOMAIN_JOB_STATS_COMPLETED to virDomainGetJobStats. We also have an async event for reporting the job state. I'll also consider removing virDomainBackupEnd for this implementation as virDomainAbortJob should be enough.
Ok, that makes sense. So we'll just have the Begin + GetXMLDesc APIs for now.
I spoke with oVirt devs who are very keen on getting this API finished and their requirements currently don't require any parallel jobs. In fact they were abusing Eric's design which only ever returned the same job ID so that they would not have to persist it.
Hah, that's gross :-)
Given that we'll need to deal with the domain job anyways for the better job infra outlined below I don't think adding these APIs will be too much of a burden in the interim so that we can appease oVirt's desire for this feature and we'll have more time to design the new job interface properly.
Ok, that's fine with me. As you say, adding a 2nd variant of the API later if we need many jobs is not the end of the world, since we'll need todo that anyway for many other APIs we already have., My preference is indeed to get something finally merged so that oVirt can use it, since we've been debating the design on list here for waaaay too long now.
Earlier in the year I queried whether we should turn the "job" into a fully fledged object, using either a string or a UUID to identify it uniquely.
https://www.redhat.com/archives/libvir-list/2019-March/msg01695.html
IOW having something like this:
Going forward I want this not only for the backup job but basically for any long running operation. We needed this for a long time but of the two long running job impls we have both are not flexible enough.
There is only one 'domain job' (migration/save/etc) and blockjobs are bound to disks.
Yeah, we kinda messed up in these two designs.
typedef struct _virDomainJob virDomainJob; typedef virDomainJob *virDomainJobPtr;
I actually started some work on this but didn't get far yet. I used the same name in my case, but I'm partially afraid that virDomainAbortJob which would not be related to these objects will be mistaken for actually working in this case.
Unfortunately I don't have any better idea.
We could just pick a completely different term. eg "virDomainOperation"
void virDomainJobFree(virDomainJobPtr job);
virDomainJobLookupByUUID(virDomainPtr job, unsigned char *uuid);
int virDomainJobGetType(virDomainJobPtr job); int virDomainJobGetUUID(virDomainJobPtr job, unsigned char *uuid); int virDomainJobGetUUIDString(virDomainJobPtr job, char *uuidstr);
virDomainJobPtr virDomainBackupBegin(virDomainPtr domain, const char *backupXML, const char *checkpointXML, unsigned int flags);
I was thinking about a super-universal API so that we don't have to redo all APIs for blockjobs. Something along
virDomainJobPtr virDomainJobBegin(virDomainPtr domain, const char *jobxml, unsigned int flags);
It would give us the flexibility to add new jobs and arguments for them via XML which is more flexible (e.g. we could easily add a virDomainBlockPull with the 'top' argument which is currently missing but qemu started to support it some time ago). On the other hand that seems a too prone to abuse.
virTypedParameters is always an option if we wish to make the specific job creation API more flexible without going all the way to XML
As of the above I'm unsure about the tradeofs between flexibility and too much flexibility in this case. But that's probably for a future discussion.
Yeah, its a tricky balance between making it super flexible, and making it more simple at an API caller POV. Basically we have two choices - consider the "job" to be just a way to monitor and manage completion of jobs that something else spawned (what we do now) - consider the "job" to be the way to control the entire lifecycle of the job, including spawning it. Regards, Daniel -- |: https://berrange.com -o- https://www.flickr.com/photos/dberrange :| |: https://libvirt.org -o- https://fstop138.berrange.com :| |: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|

From: Eric Blake <eblake@redhat.com> This one is fairly straightforward - the generator already does what we need. Signed-off-by: Eric Blake <eblake@redhat.com> Reviewed-by: Daniel P. Berrangé <berrange@redhat.com> --- src/remote/remote_driver.c | 3 ++ src/remote/remote_protocol.x | 53 +++++++++++++++++++++++++++++++++++- src/remote_protocol-structs | 28 +++++++++++++++++++ 3 files changed, 83 insertions(+), 1 deletion(-) diff --git a/src/remote/remote_driver.c b/src/remote/remote_driver.c index 9228c7d0ed..d04f2f9098 100644 --- a/src/remote/remote_driver.c +++ b/src/remote/remote_driver.c @@ -8748,6 +8748,9 @@ static virHypervisorDriver hypervisor_driver = { .domainCheckpointGetParent = remoteDomainCheckpointGetParent, /* 5.6.0 */ .domainCheckpointDelete = remoteDomainCheckpointDelete, /* 5.6.0 */ .domainGetGuestInfo = remoteDomainGetGuestInfo, /* 5.7.0 */ + .domainBackupBegin = remoteDomainBackupBegin, /* 5.9.0 */ + .domainBackupGetXMLDesc = remoteDomainBackupGetXMLDesc, /* 5.9.0 */ + .domainBackupEnd = remoteDomainBackupEnd, /* 5.9.0 */ }; static virNetworkDriver network_driver = { diff --git a/src/remote/remote_protocol.x b/src/remote/remote_protocol.x index f4e3392212..8de088be6a 100644 --- a/src/remote/remote_protocol.x +++ b/src/remote/remote_protocol.x @@ -3744,6 +3744,37 @@ struct remote_connect_set_identity_args { unsigned int flags; }; +struct remote_domain_backup_begin_args { + remote_nonnull_domain dom; + remote_string disk_xml; + remote_string checkpoint_xml; + unsigned int flags; +}; + +struct remote_domain_backup_begin_ret { + int id; +}; + +struct remote_domain_backup_get_xml_desc_args { + remote_nonnull_domain dom; + int id; + unsigned int flags; +}; + +struct remote_domain_backup_get_xml_desc_ret { + remote_nonnull_string xml; +}; + +struct remote_domain_backup_end_args { + remote_nonnull_domain dom; + int id; + unsigned int flags; +}; + +struct remote_domain_backup_end_ret { + int retcode; +}; + /*----- Protocol. -----*/ /* Define the program number, protocol version and procedure numbers here. */ @@ -6617,5 +6648,25 @@ enum remote_procedure { * @generate: client * @acl: connect:write */ - REMOTE_PROC_CONNECT_SET_IDENTITY = 419 + REMOTE_PROC_CONNECT_SET_IDENTITY = 419, + + /** + * @generate: both + * @acl: domain:checkpoint + * @acl: domain:block_write + */ + REMOTE_PROC_DOMAIN_BACKUP_BEGIN = 420, + + /** + * @generate: both + * @priority: high + * @acl: domain:read + */ + REMOTE_PROC_DOMAIN_BACKUP_GET_XML_DESC = 421, + + /** + * @generate: both + * @acl: domain:checkpoint + */ + REMOTE_PROC_DOMAIN_BACKUP_END = 422 }; diff --git a/src/remote_protocol-structs b/src/remote_protocol-structs index 51606e7473..b549ee0b3d 100644 --- a/src/remote_protocol-structs +++ b/src/remote_protocol-structs @@ -3114,6 +3114,31 @@ struct remote_connect_set_identity_args { } params; u_int flags; }; +struct remote_domain_backup_begin_args { + remote_nonnull_domain dom; + remote_string disk_xml; + remote_string checkpoint_xml; + u_int flags; +}; +struct remote_domain_backup_begin_ret { + int id; +}; +struct remote_domain_backup_get_xml_desc_args { + remote_nonnull_domain dom; + int id; + u_int flags; +}; +struct remote_domain_backup_get_xml_desc_ret { + remote_nonnull_string xml; +}; +struct remote_domain_backup_end_args { + remote_nonnull_domain dom; + int id; + u_int flags; +}; +struct remote_domain_backup_end_ret { + int retcode; +}; enum remote_procedure { REMOTE_PROC_CONNECT_OPEN = 1, REMOTE_PROC_CONNECT_CLOSE = 2, @@ -3534,4 +3559,7 @@ enum remote_procedure { REMOTE_PROC_DOMAIN_CHECKPOINT_DELETE = 417, REMOTE_PROC_DOMAIN_GET_GUEST_INFO = 418, REMOTE_PROC_CONNECT_SET_IDENTITY = 419, + REMOTE_PROC_DOMAIN_BACKUP_BEGIN = 420, + REMOTE_PROC_DOMAIN_BACKUP_GET_XML_DESC = 421, + REMOTE_PROC_DOMAIN_BACKUP_END = 422, }; -- 2.21.0

From: Eric Blake <eblake@redhat.com> Accept XML describing a generic block job, and output it again as needed. This may still need a few tweaks to match the documented XML and RNG schema. Signed-off-by: Eric Blake <eblake@redhat.com> --- src/conf/Makefile.inc.am | 2 + src/conf/backup_conf.c | 518 +++++++++++++++++++++++++++++++++++++++ src/conf/backup_conf.h | 102 ++++++++ src/conf/virconftypes.h | 3 + src/libvirt_private.syms | 8 + 5 files changed, 633 insertions(+) create mode 100644 src/conf/backup_conf.c create mode 100644 src/conf/backup_conf.h diff --git a/src/conf/Makefile.inc.am b/src/conf/Makefile.inc.am index 5035b9b524..debc6f4eef 100644 --- a/src/conf/Makefile.inc.am +++ b/src/conf/Makefile.inc.am @@ -12,6 +12,8 @@ NETDEV_CONF_SOURCES = \ $(NULL) DOMAIN_CONF_SOURCES = \ + conf/backup_conf.c \ + conf/backup_conf.h \ conf/capabilities.c \ conf/capabilities.h \ conf/checkpoint_conf.c \ diff --git a/src/conf/backup_conf.c b/src/conf/backup_conf.c new file mode 100644 index 0000000000..123e71d4d2 --- /dev/null +++ b/src/conf/backup_conf.c @@ -0,0 +1,518 @@ +/* + * backup_conf.c: domain backup XML processing + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <http://www.gnu.org/licenses/>. + */ + +#include <config.h> + +#include "configmake.h" +#include "internal.h" +#include "virbuffer.h" +#include "datatypes.h" +#include "domain_conf.h" +#include "virlog.h" +#include "viralloc.h" +#include "backup_conf.h" +#include "virstoragefile.h" +#include "viruuid.h" +#include "virfile.h" +#include "virerror.h" +#include "virxml.h" +#include "virstring.h" +#include "virhash.h" +#include "virenum.h" + +#define VIR_FROM_THIS VIR_FROM_DOMAIN + +VIR_LOG_INIT("conf.backup_conf"); + +VIR_ENUM_DECL(virDomainBackup); +VIR_ENUM_IMPL(virDomainBackup, + VIR_DOMAIN_BACKUP_TYPE_LAST, + "default", + "push", + "pull"); + +/* following values appear in the status XML */ +VIR_ENUM_DECL(virDomainBackupDiskState); +VIR_ENUM_IMPL(virDomainBackupDiskState, + VIR_DOMAIN_BACKUP_DISK_STATE_LAST, + "", + "running", + "complete", + "failed", + "cancelling", + "cancelled"); + +void +virDomainBackupDefFree(virDomainBackupDefPtr def) +{ + size_t i; + + if (!def) + return; + + g_free(def->incremental); + virStorageNetHostDefFree(1, def->server); + + for (i = 0; i < def->ndisks; i++) { + virDomainBackupDiskDefPtr disk = def->disks + i; + + g_free(disk->name); + virObjectUnref(disk->store); + } + + g_free(def->disks); + g_free(def); +} + + +static int +virDomainBackupDiskDefParseXML(xmlNodePtr node, + xmlXPathContextPtr ctxt, + virDomainBackupDiskDefPtr def, + bool push, + unsigned int flags, + virDomainXMLOptionPtr xmlopt) +{ + VIR_XPATH_NODE_AUTORESTORE(ctxt); + g_autofree char *type = NULL; + g_autofree char *driver = NULL; + g_autofree char *backup = NULL; + g_autofree char *state = NULL; + int tmp; + xmlNodePtr srcNode; + unsigned int storageSourceParseFlags = 0; + bool internal = flags & VIR_DOMAIN_BACKUP_PARSE_INTERNAL; + + if (internal) + storageSourceParseFlags = VIR_DOMAIN_DEF_PARSE_STATUS; + + ctxt->node = node; + + if (!(def->name = virXMLPropString(node, "name"))) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("missing name from disk backup element")); + return -1; + } + + def->backup = VIR_TRISTATE_BOOL_YES; + + if ((backup = virXMLPropString(node, "backup"))) { + if ((tmp = virTristateBoolTypeFromString(backup)) <= 0) { + virReportError(VIR_ERR_XML_ERROR, + _("invalid disk 'backup' state '%s'"), backup); + return -1; + } + + def->backup = tmp; + } + + /* don't parse anything else if backup is disabled */ + if (def->backup == VIR_TRISTATE_BOOL_NO) + return 0; + + if (internal) { + tmp = 0; + if (!(state = virXMLPropString(node, "state")) || + (tmp = virDomainBackupDiskStateTypeFromString(state)) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("disk '%s' backup state wrong or missing'"), def->name); + return -1; + } + + def->state = tmp; + } + + if (!(def->store = virStorageSourceNew())) + return -1; + + if ((type = virXMLPropString(node, "type"))) { + if ((def->store->type = virStorageTypeFromString(type)) <= 0 || + def->store->type == VIR_STORAGE_TYPE_VOLUME || + def->store->type == VIR_STORAGE_TYPE_DIR) { + virReportError(VIR_ERR_XML_ERROR, + _("unknown disk backup type '%s'"), type); + return -1; + } + } else { + def->store->type = VIR_STORAGE_TYPE_FILE; + } + + if (push) + srcNode = virXPathNode("./target", ctxt); + else + srcNode = virXPathNode("./scratch", ctxt); + + if (srcNode && + virDomainStorageSourceParse(srcNode, ctxt, def->store, + storageSourceParseFlags, xmlopt) < 0) + return -1; + + if ((driver = virXPathString("string(./driver/@type)", ctxt))) { + def->store->format = virStorageFileFormatTypeFromString(driver); + if (def->store->format <= 0) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("unknown disk backup driver '%s'"), driver); + return -1; + } else if (!push && def->store->format != VIR_STORAGE_FILE_QCOW2) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("pull mode requires qcow2 driver, not '%s'"), + driver); + return -1; + } + } + + return 0; +} + + +static virDomainBackupDefPtr +virDomainBackupDefParse(xmlXPathContextPtr ctxt, + virDomainXMLOptionPtr xmlopt, + unsigned int flags) +{ + g_autoptr(virDomainBackupDef) def = NULL; + g_autofree xmlNodePtr *nodes = NULL; + xmlNodePtr node = NULL; + g_autofree char *mode = NULL; + bool push; + size_t i; + int n; + + def = g_new0(virDomainBackupDef, 1); + + def->type = VIR_DOMAIN_BACKUP_TYPE_PUSH; + + if ((mode = virXMLPropString(ctxt->node, "mode"))) { + if ((def->type = virDomainBackupTypeFromString(mode)) <= 0) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("unknown backup mode '%s'"), mode); + return NULL; + } + } + + push = def->type == VIR_DOMAIN_BACKUP_TYPE_PUSH; + + if (flags & VIR_DOMAIN_BACKUP_PARSE_INTERNAL) { + g_autofree char *tmp = virXMLPropString(ctxt->node, "id"); + if (tmp && virStrToLong_i(tmp, NULL, 10, &def->id) < 0) { + virReportError(VIR_ERR_XML_ERROR, + _("invalid 'id' value '%s'"), tmp); + return NULL; + } + } + + def->incremental = virXPathString("string(./incremental)", ctxt); + + if ((node = virXPathNode("./server", ctxt))) { + if (def->type != VIR_DOMAIN_BACKUP_TYPE_PULL) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("use of <server> requires pull mode backup")); + return NULL; + } + + def->server = g_new0(virStorageNetHostDef, 1); + + if (virDomainStorageNetworkParseHost(node, def->server) < 0) + return NULL; + + if (def->server->transport == VIR_STORAGE_NET_HOST_TRANS_RDMA) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("transport rdma is not supported for <server>")); + return NULL; + } + + if (def->server->transport == VIR_STORAGE_NET_HOST_TRANS_UNIX && + def->server->socket[0] != '/') { + virReportError(VIR_ERR_XML_ERROR, + _("backup socket path '%s' must be absolute"), + def->server->socket); + return NULL; + } + } + + if ((n = virXPathNodeSet("./disks/*", ctxt, &nodes)) < 0) + return NULL; + + def->disks = g_new0(virDomainBackupDiskDef, n); + + def->ndisks = n; + for (i = 0; i < def->ndisks; i++) { + if (virDomainBackupDiskDefParseXML(nodes[i], ctxt, + &def->disks[i], push, + flags, xmlopt) < 0) + return NULL; + } + + return g_steal_pointer(&def); +} + + +virDomainBackupDefPtr +virDomainBackupDefParseString(const char *xmlStr, + virDomainXMLOptionPtr xmlopt, + unsigned int flags) +{ + virDomainBackupDefPtr ret = NULL; + g_autoptr(xmlDoc) xml = NULL; + int keepBlanksDefault = xmlKeepBlanksDefault(0); + + if ((xml = virXMLParse(NULL, xmlStr, _("(domain_backup)")))) { + xmlKeepBlanksDefault(keepBlanksDefault); + ret = virDomainBackupDefParseNode(xml, xmlDocGetRootElement(xml), + xmlopt, flags); + } + xmlKeepBlanksDefault(keepBlanksDefault); + + return ret; +} + + +virDomainBackupDefPtr +virDomainBackupDefParseNode(xmlDocPtr xml, + xmlNodePtr root, + virDomainXMLOptionPtr xmlopt, + unsigned int flags) +{ + g_autoptr(xmlXPathContext) ctxt = NULL; + g_autofree char *schema = NULL; + + if (!virXMLNodeNameEqual(root, "domainbackup")) { + virReportError(VIR_ERR_XML_ERROR, "%s", _("domainbackup")); + return NULL; + } + + if (!(flags & VIR_DOMAIN_BACKUP_PARSE_INTERNAL)) { + if (!(schema = virFileFindResource("domainbackup.rng", + abs_top_srcdir "/docs/schemas", + PKGDATADIR "/schemas"))) + return NULL; + + if (virXMLValidateAgainstSchema(schema, xml) < 0) + return NULL; + } + + if (!(ctxt = virXMLXPathContextNew(xml))) + return NULL; + + ctxt->node = root; + return virDomainBackupDefParse(ctxt, xmlopt, flags); +} + + +static int +virDomainBackupDiskDefFormat(virBufferPtr buf, + virDomainBackupDiskDefPtr disk, + bool push, + bool internal) +{ + g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER; + g_auto(virBuffer) childBuf = VIR_BUFFER_INITIALIZER; + const char *sourcename = "scratch"; + unsigned int storageSourceFormatFlags = 0; + + if (push) + sourcename = "target"; + + if (internal) + storageSourceFormatFlags |= VIR_DOMAIN_DEF_FORMAT_STATUS; + + virBufferSetChildIndent(&childBuf, buf); + + virBufferEscapeString(&attrBuf, " name='%s'", disk->name); + virBufferAsprintf(&attrBuf, " backup='%s'", virTristateBoolTypeToString(disk->backup)); + if (internal) + virBufferAsprintf(&attrBuf, " state='%s'", virDomainBackupDiskStateTypeToString(disk->state)); + + if (disk->backup == VIR_TRISTATE_BOOL_YES) { + virBufferAsprintf(&attrBuf, " type='%s'", virStorageTypeToString(disk->store->type)); + + if (disk->store->format > 0) + virBufferEscapeString(&childBuf, "<driver type='%s'/>\n", + virStorageFileFormatTypeToString(disk->store->format)); + + if (virDomainDiskSourceFormat(&childBuf, disk->store, sourcename, + 0, false, storageSourceFormatFlags, NULL) < 0) + return -1; + } + + return virXMLFormatElement(buf, "disk", &attrBuf, &childBuf); +} + + +int +virDomainBackupDefFormat(virBufferPtr buf, + virDomainBackupDefPtr def, + bool internal) +{ + g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER; + g_auto(virBuffer) childBuf = VIR_BUFFER_INITIALIZER; + g_auto(virBuffer) serverAttrBuf = VIR_BUFFER_INITIALIZER; + g_auto(virBuffer) disksChildBuf = VIR_BUFFER_INITIALIZER; + size_t i; + + virBufferSetChildIndent(&childBuf, buf); + virBufferSetChildIndent(&disksChildBuf, &childBuf); + + virBufferAsprintf(&attrBuf, " mode='%s'", virDomainBackupTypeToString(def->type)); + virBufferAsprintf(&attrBuf, " id='%d'", def->id); + + virBufferEscapeString(&childBuf, "<incremental>%s</incremental>\n", def->incremental); + + if (def->server) { + virBufferAsprintf(&serverAttrBuf, " transport='%s'", + virStorageNetHostTransportTypeToString(def->server->transport)); + virBufferEscapeString(&serverAttrBuf, " name='%s'", def->server->name); + if (def->server->port) + virBufferAsprintf(&serverAttrBuf, " port='%u'", def->server->port); + virBufferEscapeString(&serverAttrBuf, " socket='%s'", def->server->socket); + } + + if (virXMLFormatElement(&childBuf, "server", &serverAttrBuf, NULL) < 0) + return -1; + + for (i = 0; i < def->ndisks; i++) { + if (virDomainBackupDiskDefFormat(&disksChildBuf, &def->disks[i], + def->type == VIR_DOMAIN_BACKUP_TYPE_PUSH, + internal) < 0) + return -1; + } + + if (virXMLFormatElement(&childBuf, "disks", NULL, &disksChildBuf) < 0) + return -1; + + if (virXMLFormatElement(buf, "domainbackup", &attrBuf, &childBuf) < 0) + return -1; + + return 0; +} + + +static int +virDomainBackupDefAssignStore(virDomainBackupDiskDefPtr disk, + virStorageSourcePtr src, + const char *suffix) +{ + if (virStorageSourceIsEmpty(src)) { + if (disk->store) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("disk '%s' has no media"), disk->name); + return -1; + } + } else if (src->readonly) { + if (disk->store) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("backup of readonly disk '%s' makes no sense"), + disk->name); + return -1; + } + } else if (!disk->store) { + if (virStorageSourceGetActualType(src) == VIR_STORAGE_TYPE_FILE) { + if (!(disk->store = virStorageSourceNew())) + return -1; + + disk->store->type = VIR_STORAGE_TYPE_FILE; + disk->store->path = g_strdup_printf("%s.%s", src->path, suffix); + } else { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("refusing to generate file name for disk '%s'"), + disk->name); + return -1; + } + } + + return 0; +} + + +int +virDomainBackupAlignDisks(virDomainBackupDefPtr def, + virDomainDefPtr dom, + const char *suffix) +{ + g_autoptr(virHashTable) disks = NULL; + size_t i; + int ndisks; + bool backup_all = false; + + + if (!(disks = virHashNew(NULL))) + return -1; + + /* Unlikely to have a guest without disks but technically possible. */ + if (!dom->ndisks) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("domain must have at least one disk to perform backup")); + return -1; + } + + /* Double check requested disks. */ + for (i = 0; i < def->ndisks; i++) { + virDomainBackupDiskDefPtr backupdisk = &def->disks[i]; + virDomainDiskDefPtr domdisk; + + if (!(domdisk = virDomainDiskByTarget(dom, backupdisk->name))) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("no disk named '%s'"), backupdisk->name); + return -1; + } + + if (virHashAddEntry(disks, backupdisk->name, NULL) < 0) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("disk '%s' specified twice"), + backupdisk->name); + return -1; + } + + if (backupdisk->backup == VIR_TRISTATE_BOOL_YES && + virDomainBackupDefAssignStore(backupdisk, domdisk->src, suffix) < 0) + return -1; + } + + if (def->ndisks == 0) + backup_all = true; + + ndisks = def->ndisks; + if (VIR_EXPAND_N(def->disks, def->ndisks, dom->ndisks - def->ndisks) < 0) + return -1; + + for (i = 0; i < dom->ndisks; i++) { + virDomainBackupDiskDefPtr backupdisk = NULL; + virDomainDiskDefPtr domdisk = dom->disks[i]; + + if (virHashHasEntry(disks, domdisk->dst)) + continue; + + backupdisk = &def->disks[ndisks++]; + + if (VIR_STRDUP(backupdisk->name, domdisk->dst) < 0) + return -1; + + if (backup_all && + !virStorageSourceIsEmpty(domdisk->src) && + !domdisk->src->readonly) { + backupdisk->backup = VIR_TRISTATE_BOOL_YES; + + if (virDomainBackupDefAssignStore(backupdisk, domdisk->src, suffix) < 0) + return -1; + } else { + backupdisk->backup = VIR_TRISTATE_BOOL_NO; + } + } + + return 0; +} diff --git a/src/conf/backup_conf.h b/src/conf/backup_conf.h new file mode 100644 index 0000000000..3be594b7da --- /dev/null +++ b/src/conf/backup_conf.h @@ -0,0 +1,102 @@ +/* + * backup_conf.h: domain backup XML processing + * (based on domain_conf.h) + * + * Copyright (C) 2006-2019 Red Hat, Inc. + * Copyright (C) 2006-2008 Daniel P. Berrange + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "internal.h" +#include "virconftypes.h" + +/* Items related to incremental backup state */ + +typedef enum { + VIR_DOMAIN_BACKUP_TYPE_DEFAULT = 0, + VIR_DOMAIN_BACKUP_TYPE_PUSH, + VIR_DOMAIN_BACKUP_TYPE_PULL, + + VIR_DOMAIN_BACKUP_TYPE_LAST +} virDomainBackupType; + +typedef enum { + VIR_DOMAIN_BACKUP_DISK_STATE_NONE = 0, + VIR_DOMAIN_BACKUP_DISK_STATE_RUNNING, + VIR_DOMAIN_BACKUP_DISK_STATE_COMPLETE, + VIR_DOMAIN_BACKUP_DISK_STATE_FAILED, + VIR_DOMAIN_BACKUP_DISK_STATE_CANCELLING, + VIR_DOMAIN_BACKUP_DISK_STATE_CANCELLED, + VIR_DOMAIN_BACKUP_DISK_STATE_LAST +} virDomainBackupDiskState; + +/* Stores disk-backup information */ +typedef struct _virDomainBackupDiskDef virDomainBackupDiskDef; +typedef virDomainBackupDiskDef *virDomainBackupDiskDefPtr; +struct _virDomainBackupDiskDef { + char *name; /* name matching the <target dev='...' of the domain */ + virTristateBool backup; /* whether backup is requested */ + + /* details of target for push-mode, or of the scratch file for pull-mode */ + virStorageSourcePtr store; + + /* internal data */ + virDomainBackupDiskState state; +}; + +/* Stores the complete backup metadata */ +typedef struct _virDomainBackupDef virDomainBackupDef; +typedef virDomainBackupDef *virDomainBackupDefPtr; +struct _virDomainBackupDef { + /* Public XML. */ + int type; /* virDomainBackupType */ + int id; + char *incremental; + virStorageNetHostDefPtr server; /* only when type == PULL */ + + size_t ndisks; /* should not exceed dom->ndisks */ + virDomainBackupDiskDef *disks; +}; + +typedef enum { + VIR_DOMAIN_BACKUP_PARSE_INTERNAL = 1 << 0, +} virDomainBackupParseFlags; + +virDomainBackupDefPtr +virDomainBackupDefParseString(const char *xmlStr, + virDomainXMLOptionPtr xmlopt, + unsigned int flags); + +virDomainBackupDefPtr +virDomainBackupDefParseNode(xmlDocPtr xml, + xmlNodePtr root, + virDomainXMLOptionPtr xmlopt, + unsigned int flags); +void +virDomainBackupDefFree(virDomainBackupDefPtr def); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC(virDomainBackupDef, virDomainBackupDefFree); + +int +virDomainBackupDefFormat(virBufferPtr buf, + virDomainBackupDefPtr def, + bool internal); +int +virDomainBackupAlignDisks(virDomainBackupDefPtr backup, + virDomainDefPtr dom, + const char *suffix); diff --git a/src/conf/virconftypes.h b/src/conf/virconftypes.h index 462842f324..9ed9b68b65 100644 --- a/src/conf/virconftypes.h +++ b/src/conf/virconftypes.h @@ -93,6 +93,9 @@ typedef virDomainABIStability *virDomainABIStabilityPtr; typedef struct _virDomainActualNetDef virDomainActualNetDef; typedef virDomainActualNetDef *virDomainActualNetDefPtr; +typedef struct _virDomainBackupDef virDomainBackupDef; +typedef virDomainBackupDef *virDomainBackupDefPtr; + typedef struct _virDomainBIOSDef virDomainBIOSDef; typedef virDomainBIOSDef *virDomainBIOSDefPtr; diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index fdfe1e7295..b4deab520e 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -42,6 +42,14 @@ virAccessPermStorageVolTypeFromString; virAccessPermStorageVolTypeToString; +# conf/backup_conf.h +virDomainBackupAlignDisks; +virDomainBackupDefFormat; +virDomainBackupDefFree; +virDomainBackupDefParseNode; +virDomainBackupDefParseString; + + # conf/capabilities.h virCapabilitiesAddGuest; virCapabilitiesAddGuestDomain; -- 2.21.0

On 10/18/19 11:11 AM, Peter Krempa wrote:
From: Eric Blake <eblake@redhat.com>
Accept XML describing a generic block job, and output it again as needed. This may still need a few tweaks to match the documented XML and RNG schema.
Signed-off-by: Eric Blake <eblake@redhat.com> ---
I didn't closely review for what types of changes you may have made to my original patch. I know some are inevitable, like rebasing to use glib. But you may want to add your S-o-b and/or mention in the commit message what you've added, if it is non-trivial. At any rate, I can't really add my own ACK to a patch I first wrote ;) -- Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3226 Virtualization: qemu.org | libvirt.org

From: Eric Blake <eblake@redhat.com> Introduce virsh commands for performing backup jobs. Signed-off-by: Eric Blake <eblake@redhat.com> --- tools/Makefile.am | 1 + tools/virsh-backup.c | 209 +++++++++++++++++++++++++++++++++++++++++++ tools/virsh-backup.h | 21 +++++ tools/virsh.c | 2 + tools/virsh.h | 1 + tools/virsh.pod | 37 ++++++++ 6 files changed, 271 insertions(+) create mode 100644 tools/virsh-backup.c create mode 100644 tools/virsh-backup.h diff --git a/tools/Makefile.am b/tools/Makefile.am index 68320c7246..813cfaeb53 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -233,6 +233,7 @@ virt_login_shell_helper_CFLAGS = \ virsh_SOURCES = \ virsh.c virsh.h \ + virsh-backup.c virsh-backup.h\ virsh-checkpoint.c virsh-checkpoint.h \ virsh-completer.c virsh-completer.h \ virsh-completer-domain.c virsh-completer-domain.h \ diff --git a/tools/virsh-backup.c b/tools/virsh-backup.c new file mode 100644 index 0000000000..7719b4f3cc --- /dev/null +++ b/tools/virsh-backup.c @@ -0,0 +1,209 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include "virsh-backup.h" +#include "virsh-util.h" + +#include "internal.h" +#include "virfile.h" + +/* + * "backup-begin" command + */ +static const vshCmdInfo info_backup_begin[] = { + {.name = "help", + .data = N_("Start a disk backup of a live domain") + }, + {.name = "desc", + .data = N_("Use XML to start a full or incremental disk backup of a live " + "domain, optionally creating a checkpoint") + }, + {.name = NULL} +}; + +static const vshCmdOptDef opts_backup_begin[] = { + VIRSH_COMMON_OPT_DOMAIN_FULL(0), + {.name = "backupxml", + .type = VSH_OT_STRING, + .help = N_("domain backup XML"), + }, + {.name = "checkpointxml", + .type = VSH_OT_STRING, + .help = N_("domain checkpoint XML"), + }, + {.name = NULL} +}; + +static bool +cmdBackupBegin(vshControl *ctl, + const vshCmd *cmd) +{ + g_autoptr(virshDomain) dom = NULL; + const char *backup_from = NULL; + g_autofree char *backup_buffer = NULL; + const char *check_from = NULL; + g_autofree char *check_buffer = NULL; + unsigned int flags = 0; + int id; + + if (!(dom = virshCommandOptDomain(ctl, cmd, NULL))) + return false; + + if (vshCommandOptStringReq(ctl, cmd, "backupxml", &backup_from) < 0) + return false; + + if (!backup_from) { + backup_buffer = vshStrdup(ctl, "<domainbackup/>"); + } else { + if (virFileReadAll(backup_from, VSH_MAX_XML_FILE, &backup_buffer) < 0) { + vshSaveLibvirtError(); + return false; + } + } + + if (vshCommandOptStringReq(ctl, cmd, "checkpointxml", &check_from) < 0) + return false; + if (check_from) { + if (virFileReadAll(check_from, VSH_MAX_XML_FILE, &check_buffer) < 0) { + vshSaveLibvirtError(); + return false; + } + } + + if ((id = virDomainBackupBegin(dom, backup_buffer, check_buffer, flags)) < 0) + return false; + + vshPrint(ctl, _("Backup id %d started\n"), id); + + return true; +} + + +/* + * "backup-dumpxml" command + */ +static const vshCmdInfo info_backup_dumpxml[] = { + {.name = "help", + .data = N_("Dump XML for an ongoing domain block backup job") + }, + {.name = "desc", + .data = N_("Backup Dump XML") + }, + {.name = NULL} +}; + +static const vshCmdOptDef opts_backup_dumpxml[] = { + VIRSH_COMMON_OPT_DOMAIN_FULL(0), + {.name = "id", + .type = VSH_OT_INT, + .help = N_("backup job id"), + }, + {.name = NULL} +}; + +static bool +cmdBackupDumpXML(vshControl *ctl, + const vshCmd *cmd) +{ + g_autoptr(virshDomain) dom = NULL; + g_autofree char *xml = NULL; + unsigned int flags = 0; + int id = 0; + + if (vshCommandOptBool(cmd, "security-info")) + flags |= VIR_DOMAIN_XML_SECURE; + + if (vshCommandOptInt(ctl, cmd, "id", &id) < 0) + return false; + + if (!(dom = virshCommandOptDomain(ctl, cmd, NULL))) + return false; + + if (!(xml = virDomainBackupGetXMLDesc(dom, id, flags))) + return false; + + vshPrint(ctl, "%s", xml); + return true; +} + + +/* + * "backup-end" command + */ +static const vshCmdInfo info_backup_end[] = { + {.name = "help", + .data = N_("Conclude a disk backup of a live domain") + }, + {.name = "desc", + .data = N_("End a domain block backup job") + }, + {.name = NULL} +}; + +static const vshCmdOptDef opts_backup_end[] = { + VIRSH_COMMON_OPT_DOMAIN_FULL(0), + {.name = "id", + .type = VSH_OT_INT, + .help = N_("backup job id"), + }, + {.name = NULL} +}; + +static bool +cmdBackupEnd(vshControl *ctl, const vshCmd *cmd) +{ + g_autoptr(virshDomain) dom = NULL; + unsigned int flags = 0; + int id = 0; + int rc; + + if (vshCommandOptInt(ctl, cmd, "id", &id) < 0) + return false; + + if (!(dom = virshCommandOptDomain(ctl, cmd, NULL))) + return false; + + if ((rc = virDomainBackupEnd(dom, id, flags)) < 0) + return false; + + vshPrint(ctl, _("Backup id %d terminated"), id); + + return true; +} + + +const vshCmdDef backupCmds[] = { + {.name = "backup-begin", + .handler = cmdBackupBegin, + .opts = opts_backup_begin, + .info = info_backup_begin, + .flags = 0 + }, + {.name = "backup-dumpxml", + .handler = cmdBackupDumpXML, + .opts = opts_backup_dumpxml, + .info = info_backup_dumpxml, + .flags = 0 + }, + {.name = "backup-end", + .handler = cmdBackupEnd, + .opts = opts_backup_end, + .info = info_backup_end, + .flags = 0 + }, + {.name = NULL} +}; diff --git a/tools/virsh-backup.h b/tools/virsh-backup.h new file mode 100644 index 0000000000..95c2f5a424 --- /dev/null +++ b/tools/virsh-backup.h @@ -0,0 +1,21 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "virsh.h" + +extern const vshCmdDef backupCmds[]; diff --git a/tools/virsh.c b/tools/virsh.c index a3553ddd36..59c3ddb4b7 100644 --- a/tools/virsh.c +++ b/tools/virsh.c @@ -47,6 +47,7 @@ #include "virstring.h" #include "virgettext.h" +#include "virsh-backup.h" #include "virsh-checkpoint.h" #include "virsh-console.h" #include "virsh-domain.h" @@ -831,6 +832,7 @@ static const vshCmdGrp cmdGroups[] = { {VIRSH_CMD_GRP_NODEDEV, "nodedev", nodedevCmds}, {VIRSH_CMD_GRP_SECRET, "secret", secretCmds}, {VIRSH_CMD_GRP_SNAPSHOT, "snapshot", snapshotCmds}, + {VIRSH_CMD_GRP_BACKUP, "backup", backupCmds}, {VIRSH_CMD_GRP_STORAGE_POOL, "pool", storagePoolCmds}, {VIRSH_CMD_GRP_STORAGE_VOL, "volume", storageVolCmds}, {VIRSH_CMD_GRP_VIRSH, "virsh", virshCmds}, diff --git a/tools/virsh.h b/tools/virsh.h index b4e610b2a4..d84659124a 100644 --- a/tools/virsh.h +++ b/tools/virsh.h @@ -51,6 +51,7 @@ #define VIRSH_CMD_GRP_NWFILTER "Network Filter" #define VIRSH_CMD_GRP_SECRET "Secret" #define VIRSH_CMD_GRP_SNAPSHOT "Snapshot" +#define VIRSH_CMD_GRP_BACKUP "Backup" #define VIRSH_CMD_GRP_HOST_AND_HV "Host and Hypervisor" #define VIRSH_CMD_GRP_VIRSH "Virsh itself" diff --git a/tools/virsh.pod b/tools/virsh.pod index cf2798e71a..83cb315506 100644 --- a/tools/virsh.pod +++ b/tools/virsh.pod @@ -1327,6 +1327,43 @@ addresses, currently 'lease' to read DHCP leases, 'agent' to query the guest OS via an agent, or 'arp' to get IP from host's arp tables. If unspecified, 'lease' is the default. +=item B<backup-begin> I<domain> [I<backupxml>] +[I<checkpointxml> + +Begin a new backup job, and output the resulting job id on success. If +I<backupxml> is omitted, this defaults to a full backup using a push +model to filenames generated by libvirt; supplying XML allows +fine-tuning such as requesting an incremental backup relative to an +earlier checkpoint, controlling which disks participate or which +filenames are involved, or requesting the use of a pull model backup. +The B<backup-dumpxml> command shows any resulting values assigned by +libvirt. For more information on backup XML, see: +L<https://libvirt.org/formatbackup.html>. + +If I<checkpointxml> is specified, a second file with a top-level +element of <domaincheckpoint> is used to create a simultaneous +checkpoint, for doing a later incremental backup relative to the time +the backup was created. See B<checkpoint-create> for more details on +checkpoints. + +This command returns as soon as possible, and the backup job runs in +the background; the progress of a push model backup can be checked +with B<domjobinfo> or by waiting for an event with B<event> (the +progress of a pull model backup is under the control of whatever third +party connects to the NBD export). The job is ended with B<backup-end>. + +=item B<backup-dumpxml> I<domain> [I<id>] + +Output XML describing the backup job I<id>. The default for I<id> is +0, which works as long as there are no parallel jobs; it is also +possible to use the positive id printed by B<backup-begin> on success. + +=item B<backup-end> I<domain> [I<id>] + +End the current backup job I<id>. The default for I<id> is 0, which +works as long as there are no parallel jobs; it is also possible to +use the positive id printed by B<backup-begin> on success. + =item B<domiflist> I<domain> [I<--inactive>] Print a table showing the brief information of all virtual interfaces -- 2.21.0

A backup job may consist of many backup sub-blockjobs. Add the new blockjob type and add all type converter strings. Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- examples/c/misc/event-test.c | 3 +++ include/libvirt/libvirt-domain.h | 3 +++ src/conf/domain_conf.c | 2 +- src/qemu/qemu_blockjob.c | 2 ++ src/qemu/qemu_blockjob.h | 1 + src/qemu/qemu_domain.c | 2 ++ src/qemu/qemu_driver.c | 1 + tools/virsh-domain.c | 4 +++- 8 files changed, 16 insertions(+), 2 deletions(-) diff --git a/examples/c/misc/event-test.c b/examples/c/misc/event-test.c index 5db572175d..ae282a5027 100644 --- a/examples/c/misc/event-test.c +++ b/examples/c/misc/event-test.c @@ -891,6 +891,9 @@ blockJobTypeToStr(int type) case VIR_DOMAIN_BLOCK_JOB_TYPE_ACTIVE_COMMIT: return "active layer block commit"; + + case VIR_DOMAIN_BLOCK_JOB_TYPE_BACKUP: + return "backup"; } return "unknown"; diff --git a/include/libvirt/libvirt-domain.h b/include/libvirt/libvirt-domain.h index 2d9f69f7d4..08829a613a 100644 --- a/include/libvirt/libvirt-domain.h +++ b/include/libvirt/libvirt-domain.h @@ -2446,6 +2446,9 @@ typedef enum { * exists as long as sync is active */ VIR_DOMAIN_BLOCK_JOB_TYPE_ACTIVE_COMMIT = 4, + /* Backup (virDomainBackupBegin), job exists until virDomainBackupEnd */ + VIR_DOMAIN_BLOCK_JOB_TYPE_BACKUP = 5, + # ifdef VIR_ENUM_SENTINELS VIR_DOMAIN_BLOCK_JOB_TYPE_LAST # endif diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index e12d676a8c..18c6e48c70 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -1227,7 +1227,7 @@ VIR_ENUM_IMPL(virDomainOsDefFirmware, VIR_ENUM_DECL(virDomainBlockJob); VIR_ENUM_IMPL(virDomainBlockJob, VIR_DOMAIN_BLOCK_JOB_TYPE_LAST, - "", "", "copy", "", "active-commit", + "", "", "copy", "", "active-commit", "", ); VIR_ENUM_IMPL(virDomainMemoryModel, diff --git a/src/qemu/qemu_blockjob.c b/src/qemu/qemu_blockjob.c index e12dcde729..6d66d75ed7 100644 --- a/src/qemu/qemu_blockjob.c +++ b/src/qemu/qemu_blockjob.c @@ -65,6 +65,7 @@ VIR_ENUM_IMPL(qemuBlockjob, "copy", "commit", "active-commit", + "backup", "", "create"); @@ -1285,6 +1286,7 @@ qemuBlockJobEventProcessConcludedTransition(qemuBlockJobDataPtr job, qemuBlockJobProcessEventConcludedCopyAbort(driver, vm, job, asyncJob); break; + case QEMU_BLOCKJOB_TYPE_BACKUP: case QEMU_BLOCKJOB_TYPE_NONE: case QEMU_BLOCKJOB_TYPE_INTERNAL: case QEMU_BLOCKJOB_TYPE_LAST: diff --git a/src/qemu/qemu_blockjob.h b/src/qemu/qemu_blockjob.h index d8da918f2f..8b28990801 100644 --- a/src/qemu/qemu_blockjob.h +++ b/src/qemu/qemu_blockjob.h @@ -60,6 +60,7 @@ typedef enum { QEMU_BLOCKJOB_TYPE_COPY = VIR_DOMAIN_BLOCK_JOB_TYPE_COPY, QEMU_BLOCKJOB_TYPE_COMMIT = VIR_DOMAIN_BLOCK_JOB_TYPE_COMMIT, QEMU_BLOCKJOB_TYPE_ACTIVE_COMMIT = VIR_DOMAIN_BLOCK_JOB_TYPE_ACTIVE_COMMIT, + QEMU_BLOCKJOB_TYPE_BACKUP = VIR_DOMAIN_BLOCK_JOB_TYPE_BACKUP, /* Additional enum values local to qemu */ QEMU_BLOCKJOB_TYPE_INTERNAL, QEMU_BLOCKJOB_TYPE_CREATE, diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index e330d2390c..d5a2ddaa8c 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -2556,6 +2556,7 @@ qemuDomainObjPrivateXMLFormatBlockjobIterator(void *payload, virBufferAddLit(&attrBuf, " shallownew='yes'"); break; + case QEMU_BLOCKJOB_TYPE_BACKUP: case QEMU_BLOCKJOB_TYPE_NONE: case QEMU_BLOCKJOB_TYPE_INTERNAL: case QEMU_BLOCKJOB_TYPE_LAST: @@ -3139,6 +3140,7 @@ qemuDomainObjPrivateXMLParseBlockjobDataSpecific(qemuBlockJobDataPtr job, } break; + case QEMU_BLOCKJOB_TYPE_BACKUP: case QEMU_BLOCKJOB_TYPE_NONE: case QEMU_BLOCKJOB_TYPE_INTERNAL: case QEMU_BLOCKJOB_TYPE_LAST: diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 501c909e49..330345b5de 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -17434,6 +17434,7 @@ qemuDomainBlockPivot(virQEMUDriverPtr driver, case QEMU_BLOCKJOB_TYPE_PULL: case QEMU_BLOCKJOB_TYPE_COMMIT: + case QEMU_BLOCKJOB_TYPE_BACKUP: case QEMU_BLOCKJOB_TYPE_INTERNAL: case QEMU_BLOCKJOB_TYPE_CREATE: virReportError(VIR_ERR_OPERATION_INVALID, diff --git a/tools/virsh-domain.c b/tools/virsh-domain.c index 5ecc8a229c..6a658a33a1 100644 --- a/tools/virsh-domain.c +++ b/tools/virsh-domain.c @@ -2562,7 +2562,9 @@ VIR_ENUM_IMPL(virshDomainBlockJob, N_("Block Pull"), N_("Block Copy"), N_("Block Commit"), - N_("Active Block Commit")); + N_("Active Block Commit"), + N_("Backup"), +); static const char * virshDomainBlockJobToString(int type) -- 2.21.0

On 10/18/19 11:11 AM, Peter Krempa wrote:
A backup job may consist of many backup sub-blockjobs. Add the new blockjob type and add all type converter strings.
Signed-off-by: Peter Krempa <pkrempa@redhat.com> ---
Reviewed-by: Eric Blake <eblake@redhat.com> -- Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3226 Virtualization: qemu.org | libvirt.org

Implement the transaction actions generator for blockdev-backup. Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_monitor.c | 13 +++++++++++++ src/qemu/qemu_monitor.h | 15 +++++++++++++++ src/qemu/qemu_monitor_json.c | 29 +++++++++++++++++++++++++++++ src/qemu/qemu_monitor_json.h | 8 ++++++++ tests/qemumonitorjsontest.c | 8 +++++++- 5 files changed, 72 insertions(+), 1 deletion(-) diff --git a/src/qemu/qemu_monitor.c b/src/qemu/qemu_monitor.c index 57229a68c0..6fd2c72769 100644 --- a/src/qemu/qemu_monitor.c +++ b/src/qemu/qemu_monitor.c @@ -4579,3 +4579,16 @@ qemuMonitorTransactionSnapshotBlockdev(virJSONValuePtr actions, { return qemuMonitorJSONTransactionSnapshotBlockdev(actions, node, overlay); } + + +int +qemuMonitorTransactionBackup(virJSONValuePtr actions, + const char *device, + const char *jobname, + const char *target, + const char *bitmap, + qemuMonitorTransactionBackupSyncMode syncmode) +{ + return qemuMonitorJSONTransactionBackup(actions, device, jobname, target, + bitmap, syncmode); +} diff --git a/src/qemu/qemu_monitor.h b/src/qemu/qemu_monitor.h index a1c980e40e..51c421d6ee 100644 --- a/src/qemu/qemu_monitor.h +++ b/src/qemu/qemu_monitor.h @@ -1379,3 +1379,18 @@ int qemuMonitorTransactionSnapshotBlockdev(virJSONValuePtr actions, const char *node, const char *overlay); + +typedef enum { + QEMU_MONITOR_TRANSACTION_BACKUP_SYNC_MODE_NONE = 0, + QEMU_MONITOR_TRANSACTION_BACKUP_SYNC_MODE_INCREMENTAL, + QEMU_MONITOR_TRANSACTION_BACKUP_SYNC_MODE_FULL, + QEMU_MONITOR_TRANSACTION_BACKUP_SYNC_MODE_LAST, +} qemuMonitorTransactionBackupSyncMode; + +int +qemuMonitorTransactionBackup(virJSONValuePtr actions, + const char *device, + const char *jobname, + const char *target, + const char *bitmap, + qemuMonitorTransactionBackupSyncMode syncmode); diff --git a/src/qemu/qemu_monitor_json.c b/src/qemu/qemu_monitor_json.c index 545911069e..51603d2631 100644 --- a/src/qemu/qemu_monitor_json.c +++ b/src/qemu/qemu_monitor_json.c @@ -9254,6 +9254,35 @@ qemuMonitorJSONTransactionSnapshotBlockdev(virJSONValuePtr actions, NULL); } +VIR_ENUM_DECL(qemuMonitorTransactionBackupSyncMode); +VIR_ENUM_IMPL(qemuMonitorTransactionBackupSyncMode, + QEMU_MONITOR_TRANSACTION_BACKUP_SYNC_MODE_LAST, + "none", + "incremental", + "full"); + +int +qemuMonitorJSONTransactionBackup(virJSONValuePtr actions, + const char *device, + const char *jobname, + const char *target, + const char *bitmap, + qemuMonitorTransactionBackupSyncMode syncmode) +{ + const char *syncmodestr = qemuMonitorTransactionBackupSyncModeTypeToString(syncmode); + + return qemuMonitorJSONTransactionAdd(actions, + "blockdev-backup", + "s:device", device, + "s:job-id", jobname, + "s:target", target, + "s:sync", syncmodestr, + "S:bitmap", bitmap, + "T:auto-finalize", VIR_TRISTATE_BOOL_YES, + "T:auto-dismiss", VIR_TRISTATE_BOOL_NO, + NULL); +} + static qemuMonitorJobInfoPtr qemuMonitorJSONGetJobInfoOne(virJSONValuePtr data) diff --git a/src/qemu/qemu_monitor_json.h b/src/qemu/qemu_monitor_json.h index 6617783797..dc291fa53f 100644 --- a/src/qemu/qemu_monitor_json.h +++ b/src/qemu/qemu_monitor_json.h @@ -667,3 +667,11 @@ int qemuMonitorJSONTransactionSnapshotBlockdev(virJSONValuePtr actions, const char *node, const char *overlay); + +int +qemuMonitorJSONTransactionBackup(virJSONValuePtr actions, + const char *device, + const char *jobname, + const char *target, + const char *bitmap, + qemuMonitorTransactionBackupSyncMode syncmode); diff --git a/tests/qemumonitorjsontest.c b/tests/qemumonitorjsontest.c index cefa0f08cf..0ea4579866 100644 --- a/tests/qemumonitorjsontest.c +++ b/tests/qemumonitorjsontest.c @@ -2992,7 +2992,13 @@ testQemuMonitorJSONTransaction(const void *opaque) qemuMonitorTransactionBitmapDisable(actions, "node4", "bitmap4") < 0 || qemuMonitorTransactionBitmapMerge(actions, "node5", "bitmap5", &mergebitmaps) < 0 || qemuMonitorTransactionSnapshotLegacy(actions, "dev6", "path", "qcow2", true) < 0 || - qemuMonitorTransactionSnapshotBlockdev(actions, "node7", "overlay7") < 0) + qemuMonitorTransactionSnapshotBlockdev(actions, "node7", "overlay7") < 0 || + qemuMonitorTransactionBackup(actions, "dev8", "job8", "target8", "bitmap8", + QEMU_MONITOR_TRANSACTION_BACKUP_SYNC_MODE_NONE) < 0 || + qemuMonitorTransactionBackup(actions, "dev9", "job9", "target9", "bitmap9", + QEMU_MONITOR_TRANSACTION_BACKUP_SYNC_MODE_INCREMENTAL) < 0 || + qemuMonitorTransactionBackup(actions, "devA", "jobA", "targetA", "bitmapA", + QEMU_MONITOR_TRANSACTION_BACKUP_SYNC_MODE_FULL) < 0) return -1; if (qemuMonitorTestAddItem(test, "transaction", "{\"return\":{}}") < 0) -- 2.21.0

On 10/18/19 11:11 AM, Peter Krempa wrote:
Implement the transaction actions generator for blockdev-backup.
Signed-off-by: Peter Krempa <pkrempa@redhat.com> ---
Reviewed-by: Eric Blake <eblake@redhat.com> -- Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3226 Virtualization: qemu.org | libvirt.org

Store the data of a backup job along with the index counter for new backup jobs in the status XML. Currently we will support only one backup job and thus there's no necessity to add arrays of jobs. Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_domain.c | 72 ++++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_domain.h | 4 +++ 2 files changed, 76 insertions(+) diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index d5a2ddaa8c..738f06bc5b 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -61,6 +61,7 @@ #include "locking/domain_lock.h" #include "virdomainsnapshotobjlist.h" #include "virdomaincheckpointobjlist.h" +#include "backup_conf.h" #ifdef MAJOR_IN_MKDEV # include <sys/mkdev.h> @@ -2108,6 +2109,8 @@ qemuDomainObjPrivateAlloc(void *opaque) priv->migMaxBandwidth = QEMU_DOMAIN_MIG_BANDWIDTH_MAX; priv->driver = opaque; + priv->backupnextid = 1; + return priv; error: @@ -2177,6 +2180,10 @@ qemuDomainObjPrivateDataClear(qemuDomainObjPrivatePtr priv) virHashRemoveAll(priv->blockjobs); virHashRemoveAll(priv->dbusVMStates); + + virDomainBackupDefFree(priv->backup); + priv->backup = NULL; + priv->backupnextid = 1; } @@ -2593,6 +2600,29 @@ qemuDomainObjPrivateXMLFormatBlockjobs(virBufferPtr buf, } +static int +qemuDomainObjPrivateXMLFormatBackups(virBufferPtr buf, + virDomainObjPtr vm) +{ + qemuDomainObjPrivatePtr priv = vm->privateData; + g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER; + g_auto(virBuffer) childBuf = VIR_BUFFER_INITIALIZER; + + virBufferSetChildIndent(&childBuf, buf); + + if (!virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_INCREMENTAL_BACKUP)) + return 0; + + virBufferAsprintf(&attrBuf, " nextid='%llu'", priv->backupnextid); + + if (priv->backup && + virDomainBackupDefFormat(&childBuf, priv->backup, true) < 0) + return -1; + + return virXMLFormatElement(buf, "backups", &attrBuf, &childBuf); +} + + void qemuDomainObjPrivateXMLFormatAllowReboot(virBufferPtr buf, virTristateBool allowReboot) @@ -2907,6 +2937,9 @@ qemuDomainObjPrivateXMLFormat(virBufferPtr buf, if (qemuDomainObjPrivateXMLFormatSlirp(buf, vm) < 0) return -1; + if (qemuDomainObjPrivateXMLFormatBackups(buf, vm) < 0) + return -1; + return 0; } @@ -3272,6 +3305,42 @@ qemuDomainObjPrivateXMLParseBlockjobs(virDomainObjPtr vm, } +static int +qemuDomainObjPrivateXMLParseBackups(qemuDomainObjPrivatePtr priv, + xmlXPathContextPtr ctxt) +{ + g_autofree xmlNodePtr *nodes = NULL; + ssize_t nnodes = 0; + + priv->backupnextid = 1; + if (virXPathULongLong("string(./backups/@nextid)", ctxt, + &priv->backupnextid) == -2) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("failed to parse next backup job id")); + return -1; + } + + if ((nnodes = virXPathNodeSet("./backups/domainbackup", ctxt, &nodes)) < 0) + return -1; + + if (nnodes > 1) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("only one backup job is supported")); + return -1; + } + + if (nnodes == 0) + return 0; + + if (!(priv->backup = virDomainBackupDefParseNode(ctxt->doc, nodes[0], + priv->driver->xmlopt, + VIR_DOMAIN_BACKUP_PARSE_INTERNAL))) + return -1; + + return 0; +} + + int qemuDomainObjPrivateXMLParseAllowReboot(xmlXPathContextPtr ctxt, virTristateBool *allowReboot) @@ -3696,6 +3765,9 @@ qemuDomainObjPrivateXMLParse(xmlXPathContextPtr ctxt, if (qemuDomainObjPrivateXMLParseBlockjobs(vm, priv, ctxt) < 0) goto error; + if (qemuDomainObjPrivateXMLParseBackups(priv, ctxt) < 0) + goto error; + qemuDomainStorageIdReset(priv); if (virXPathULongLong("string(./nodename/@index)", ctxt, &priv->nodenameindex) == -2) { diff --git a/src/qemu/qemu_domain.h b/src/qemu/qemu_domain.h index 5b3d84cea7..765801895c 100644 --- a/src/qemu/qemu_domain.h +++ b/src/qemu/qemu_domain.h @@ -396,6 +396,10 @@ struct _qemuDomainObjPrivate { virHashTablePtr dbusVMStates; bool disableSlirp; + + /* Running backup jobs. */ + unsigned long long backupnextid; + virDomainBackupDefPtr backup; }; #define QEMU_DOMAIN_PRIVATE(vm) \ -- 2.21.0

On 10/18/19 11:11 AM, Peter Krempa wrote:
Store the data of a backup job along with the index counter for new backup jobs in the status XML. Currently we will support only one backup job and thus there's no necessity to add arrays of jobs.
Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_domain.c | 72 ++++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_domain.h | 4 +++ 2 files changed, 76 insertions(+)
diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index d5a2ddaa8c..738f06bc5b 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -61,6 +61,7 @@ #include "locking/domain_lock.h" #include "virdomainsnapshotobjlist.h" #include "virdomaincheckpointobjlist.h" +#include "backup_conf.h"
Do we try to keep this list sorted in any way? Reviewed-by: Eric Blake <eblake@redhat.com> -- Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3226 Virtualization: qemu.org | libvirt.org

A backup blockjob needs to be able to notify the parent backup job as well as track all data to be able to clean up the bitmap and blockdev used for the backup. Add the data structure, job allocation function and status XML formatter and parser. Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_blockjob.c | 33 +++++++++++++++++++++++++++++++++ src/qemu/qemu_blockjob.h | 18 ++++++++++++++++++ src/qemu/qemu_domain.c | 21 +++++++++++++++++++++ 3 files changed, 72 insertions(+) diff --git a/src/qemu/qemu_blockjob.c b/src/qemu/qemu_blockjob.c index 6d66d75ed7..6f190b3485 100644 --- a/src/qemu/qemu_blockjob.c +++ b/src/qemu/qemu_blockjob.c @@ -83,6 +83,11 @@ qemuBlockJobDataDispose(void *obj) if (job->type == QEMU_BLOCKJOB_TYPE_CREATE) virObjectUnref(job->data.create.src); + if (job->type == QEMU_BLOCKJOB_TYPE_BACKUP) { + virObjectUnref(job->data.backup.store); + g_free(job->data.backup.bitmap); + } + g_free(job->name); g_free(job->errmsg); } @@ -350,6 +355,34 @@ qemuBlockJobDiskNewCopy(virDomainObjPtr vm, } +qemuBlockJobDataPtr +qemuBlockJobDiskNewBackup(virDomainObjPtr vm, + virDomainDiskDefPtr disk, + virStorageSourcePtr store, + const char *bitmap, + int jobid) +{ + g_autoptr(qemuBlockJobData) job = NULL; + g_autofree char *jobname = NULL; + + jobname = g_strdup_printf("backup-%s-%s", disk->dst, disk->src->nodeformat); + + if (!(job = qemuBlockJobDataNew(QEMU_BLOCKJOB_TYPE_BACKUP, jobname))) + return NULL; + + job->data.backup.bitmap = g_strdup(bitmap); + job->data.backup.store = virObjectRef(store); + job->data.backup.jobid = jobid; + + /* backup jobs are usually started in bulk by transaction so the caller + * shall save the status XML */ + if (qemuBlockJobRegister(job, vm, disk, false) < 0) + return NULL; + + return g_steal_pointer(&job); +} + + /** * qemuBlockJobDiskGetJob: * @disk: disk definition diff --git a/src/qemu/qemu_blockjob.h b/src/qemu/qemu_blockjob.h index 8b28990801..21b07ff884 100644 --- a/src/qemu/qemu_blockjob.h +++ b/src/qemu/qemu_blockjob.h @@ -106,6 +106,16 @@ struct _qemuBlockJobCopyData { }; +typedef struct _qemuBlockJobBackupData qemuBlockJobBackupData; +typedef qemuBlockJobBackupData *qemuBlockJobDataBackupPtr; + +struct _qemuBlockJobBackupData { + virStorageSourcePtr store; + char *bitmap; + int jobid; +}; + + typedef struct _qemuBlockJobData qemuBlockJobData; typedef qemuBlockJobData *qemuBlockJobDataPtr; @@ -123,6 +133,7 @@ struct _qemuBlockJobData { qemuBlockJobCommitData commit; qemuBlockJobCreateData create; qemuBlockJobCopyData copy; + qemuBlockJobBackupData backup; } data; int type; /* qemuBlockJobType */ @@ -181,6 +192,13 @@ qemuBlockJobDiskNewCopy(virDomainObjPtr vm, bool shallow, bool reuse); +qemuBlockJobDataPtr +qemuBlockJobDiskNewBackup(virDomainObjPtr vm, + virDomainDiskDefPtr disk, + virStorageSourcePtr store, + const char *bitmap, + int jobid); + qemuBlockJobDataPtr qemuBlockJobDiskGetJob(virDomainDiskDefPtr disk) ATTRIBUTE_NONNULL(1); diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index 738f06bc5b..b649341c9c 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -2564,6 +2564,16 @@ qemuDomainObjPrivateXMLFormatBlockjobIterator(void *payload, break; case QEMU_BLOCKJOB_TYPE_BACKUP: + virBufferEscapeString(&childBuf, "<bitmap name='%s'/>\n", job->data.backup.bitmap); + virBufferAsprintf(&childBuf, "<backup id='%d'/>\n", job->data.backup.jobid); + if (job->data.backup.store && + qemuDomainObjPrivateXMLFormatBlockjobFormatSource(&childBuf, + "store", + job->data.backup.store, + data->xmlopt, + false) < 0) + return -1; + case QEMU_BLOCKJOB_TYPE_NONE: case QEMU_BLOCKJOB_TYPE_INTERNAL: case QEMU_BLOCKJOB_TYPE_LAST: @@ -3174,6 +3184,17 @@ qemuDomainObjPrivateXMLParseBlockjobDataSpecific(qemuBlockJobDataPtr job, break; case QEMU_BLOCKJOB_TYPE_BACKUP: + if (!(job->data.backup.bitmap = virXPathString("string(./bitmap/@name)", ctxt))) + goto broken; + + if (virXPathInt("string(./backup/@id)", ctxt, &job->data.backup.jobid) < 0) + goto broken; + + if (!(tmp = virXPathNode("./store", ctxt)) || + !(job->data.backup.store = qemuDomainObjPrivateXMLParseBlockjobChain(tmp, ctxt, xmlopt))) + goto broken; + break; + case QEMU_BLOCKJOB_TYPE_NONE: case QEMU_BLOCKJOB_TYPE_INTERNAL: case QEMU_BLOCKJOB_TYPE_LAST: -- 2.21.0

On 10/18/19 11:11 AM, Peter Krempa wrote:
A backup blockjob needs to be able to notify the parent backup job as well as track all data to be able to clean up the bitmap and blockdev used for the backup.
Add the data structure, job allocation function and status XML formatter and parser.
Signed-off-by: Peter Krempa <pkrempa@redhat.com> ---
+++ b/src/qemu/qemu_domain.c @@ -2564,6 +2564,16 @@ qemuDomainObjPrivateXMLFormatBlockjobIterator(void *payload, break;
case QEMU_BLOCKJOB_TYPE_BACKUP: + virBufferEscapeString(&childBuf, "<bitmap name='%s'/>\n", job->data.backup.bitmap); + virBufferAsprintf(&childBuf, "<backup id='%d'/>\n", job->data.backup.jobid); + if (job->data.backup.store && + qemuDomainObjPrivateXMLFormatBlockjobFormatSource(&childBuf, + "store", + job->data.backup.store, + data->xmlopt, + false) < 0)
output of <store> is optional...
+ return -1; + case QEMU_BLOCKJOB_TYPE_NONE: case QEMU_BLOCKJOB_TYPE_INTERNAL: case QEMU_BLOCKJOB_TYPE_LAST: @@ -3174,6 +3184,17 @@ qemuDomainObjPrivateXMLParseBlockjobDataSpecific(qemuBlockJobDataPtr job, break;
case QEMU_BLOCKJOB_TYPE_BACKUP: + if (!(job->data.backup.bitmap = virXPathString("string(./bitmap/@name)", ctxt))) + goto broken; + + if (virXPathInt("string(./backup/@id)", ctxt, &job->data.backup.jobid) < 0) + goto broken; + + if (!(tmp = virXPathNode("./store", ctxt)) || + !(job->data.backup.store = qemuDomainObjPrivateXMLParseBlockjobChain(tmp, ctxt, xmlopt))) + goto broken;
... so shouldn't this be: if ((tmp = ...) && !(store = ...)) so we don't goto broken when it is not present? -- Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3226 Virtualization: qemu.org | libvirt.org

Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- .../qemustatusxml2xmldata/backup-pull-in.xml | 607 ++++++++++++++++++ .../qemustatusxml2xmldata/backup-pull-out.xml | 1 + tests/qemuxml2xmltest.c | 2 + 3 files changed, 610 insertions(+) create mode 100644 tests/qemustatusxml2xmldata/backup-pull-in.xml create mode 120000 tests/qemustatusxml2xmldata/backup-pull-out.xml diff --git a/tests/qemustatusxml2xmldata/backup-pull-in.xml b/tests/qemustatusxml2xmldata/backup-pull-in.xml new file mode 100644 index 0000000000..afb23bfe96 --- /dev/null +++ b/tests/qemustatusxml2xmldata/backup-pull-in.xml @@ -0,0 +1,607 @@ +<domstatus state='running' reason='booted' pid='7690'> + <taint flag='high-privileges'/> + <monitor path='/var/lib/libvirt/qemu/domain-4-copy/monitor.sock' type='unix'/> + <namespaces> + <mount/> + </namespaces> + <vcpus> + <vcpu id='0' pid='7696'/> + </vcpus> + <qemuCaps> + <flag name='kvm'/> + <flag name='no-hpet'/> + <flag name='spice'/> + <flag name='hda-duplex'/> + <flag name='ccid-emulated'/> + <flag name='ccid-passthru'/> + <flag name='virtio-tx-alg'/> + <flag name='virtio-blk-pci.ioeventfd'/> + <flag name='sga'/> + <flag name='virtio-blk-pci.event_idx'/> + <flag name='virtio-net-pci.event_idx'/> + <flag name='piix3-usb-uhci'/> + <flag name='piix4-usb-uhci'/> + <flag name='usb-ehci'/> + <flag name='ich9-usb-ehci1'/> + <flag name='vt82c686b-usb-uhci'/> + <flag name='pci-ohci'/> + <flag name='usb-redir'/> + <flag name='usb-hub'/> + <flag name='ich9-ahci'/> + <flag name='no-acpi'/> + <flag name='virtio-blk-pci.scsi'/> + <flag name='scsi-disk.channel'/> + <flag name='scsi-block'/> + <flag name='transaction'/> + <flag name='block-job-async'/> + <flag name='scsi-cd'/> + <flag name='ide-cd'/> + <flag name='hda-micro'/> + <flag name='dump-guest-memory'/> + <flag name='nec-usb-xhci'/> + <flag name='balloon-event'/> + <flag name='lsi'/> + <flag name='virtio-scsi-pci'/> + <flag name='blockio'/> + <flag name='disable-s3'/> + <flag name='disable-s4'/> + <flag name='usb-redir.filter'/> + <flag name='ide-drive.wwn'/> + <flag name='scsi-disk.wwn'/> + <flag name='seccomp-sandbox'/> + <flag name='reboot-timeout'/> + <flag name='seamless-migration'/> + <flag name='block-commit'/> + <flag name='vnc'/> + <flag name='drive-mirror'/> + <flag name='blockdev-snapshot-sync'/> + <flag name='qxl'/> + <flag name='VGA'/> + <flag name='cirrus-vga'/> + <flag name='vmware-svga'/> + <flag name='device-video-primary'/> + <flag name='usb-serial'/> + <flag name='nbd-server'/> + <flag name='virtio-rng'/> + <flag name='rng-random'/> + <flag name='rng-egd'/> + <flag name='megasas'/> + <flag name='tpm-passthrough'/> + <flag name='tpm-tis'/> + <flag name='pci-bridge'/> + <flag name='vfio-pci'/> + <flag name='mem-merge'/> + <flag name='drive-discard'/> + <flag name='mlock'/> + <flag name='device-del-event'/> + <flag name='dmi-to-pci-bridge'/> + <flag name='i440fx-pci-hole64-size'/> + <flag name='q35-pci-hole64-size'/> + <flag name='usb-storage'/> + <flag name='usb-storage.removable'/> + <flag name='ich9-intel-hda'/> + <flag name='kvm-pit-lost-tick-policy'/> + <flag name='boot-strict'/> + <flag name='pvpanic'/> + <flag name='spice-file-xfer-disable'/> + <flag name='usb-kbd'/> + <flag name='msg-timestamp'/> + <flag name='active-commit'/> + <flag name='change-backing-file'/> + <flag name='memory-backend-ram'/> + <flag name='numa'/> + <flag name='memory-backend-file'/> + <flag name='usb-audio'/> + <flag name='rtc-reset-reinjection'/> + <flag name='splash-timeout'/> + <flag name='iothread'/> + <flag name='migrate-rdma'/> + <flag name='ivshmem'/> + <flag name='drive-iotune-max'/> + <flag name='VGA.vgamem_mb'/> + <flag name='vmware-svga.vgamem_mb'/> + <flag name='qxl.vgamem_mb'/> + <flag name='pc-dimm'/> + <flag name='machine-vmport-opt'/> + <flag name='aes-key-wrap'/> + <flag name='dea-key-wrap'/> + <flag name='pci-serial'/> + <flag name='vhost-user-multiqueue'/> + <flag name='migration-event'/> + <flag name='ioh3420'/> + <flag name='x3130-upstream'/> + <flag name='xio3130-downstream'/> + <flag name='rtl8139'/> + <flag name='e1000'/> + <flag name='virtio-net'/> + <flag name='gic-version'/> + <flag name='incoming-defer'/> + <flag name='virtio-gpu'/> + <flag name='virtio-gpu.virgl'/> + <flag name='virtio-keyboard'/> + <flag name='virtio-mouse'/> + <flag name='virtio-tablet'/> + <flag name='virtio-input-host'/> + <flag name='chardev-file-append'/> + <flag name='ich9-disable-s3'/> + <flag name='ich9-disable-s4'/> + <flag name='vserport-change-event'/> + <flag name='virtio-balloon-pci.deflate-on-oom'/> + <flag name='mptsas1068'/> + <flag name='spice-gl'/> + <flag name='qxl.vram64_size_mb'/> + <flag name='chardev-logfile'/> + <flag name='debug-threads'/> + <flag name='secret'/> + <flag name='pxb'/> + <flag name='pxb-pcie'/> + <flag name='device-tray-moved-event'/> + <flag name='nec-usb-xhci-ports'/> + <flag name='virtio-scsi-pci.iothread'/> + <flag name='name-guest'/> + <flag name='qxl.max_outputs'/> + <flag name='spice-unix'/> + <flag name='drive-detect-zeroes'/> + <flag name='tls-creds-x509'/> + <flag name='intel-iommu'/> + <flag name='smm'/> + <flag name='virtio-pci-disable-legacy'/> + <flag name='query-hotpluggable-cpus'/> + <flag name='virtio-net.rx_queue_size'/> + <flag name='virtio-vga'/> + <flag name='drive-iotune-max-length'/> + <flag name='ivshmem-plain'/> + <flag name='ivshmem-doorbell'/> + <flag name='query-qmp-schema'/> + <flag name='gluster.debug_level'/> + <flag name='vhost-scsi'/> + <flag name='drive-iotune-group'/> + <flag name='query-cpu-model-expansion'/> + <flag name='virtio-net.host_mtu'/> + <flag name='spice-rendernode'/> + <flag name='nvdimm'/> + <flag name='pcie-root-port'/> + <flag name='query-cpu-definitions'/> + <flag name='block-write-threshold'/> + <flag name='query-named-block-nodes'/> + <flag name='cpu-cache'/> + <flag name='qemu-xhci'/> + <flag name='kernel-irqchip'/> + <flag name='kernel-irqchip.split'/> + <flag name='intel-iommu.intremap'/> + <flag name='intel-iommu.caching-mode'/> + <flag name='intel-iommu.eim'/> + <flag name='intel-iommu.device-iotlb'/> + <flag name='virtio.iommu_platform'/> + <flag name='virtio.ats'/> + <flag name='loadparm'/> + <flag name='vnc-multi-servers'/> + <flag name='virtio-net.tx_queue_size'/> + <flag name='chardev-reconnect'/> + <flag name='virtio-gpu.max_outputs'/> + <flag name='vxhs'/> + <flag name='virtio-blk.num-queues'/> + <flag name='vmcoreinfo'/> + <flag name='numa.dist'/> + <flag name='disk-share-rw'/> + <flag name='iscsi.password-secret'/> + <flag name='isa-serial'/> + <flag name='dump-completed'/> + <flag name='qcow2-luks'/> + <flag name='pcie-pci-bridge'/> + <flag name='seccomp-blacklist'/> + <flag name='query-cpus-fast'/> + <flag name='disk-write-cache'/> + <flag name='nbd-tls'/> + <flag name='tpm-crb'/> + <flag name='pr-manager-helper'/> + <flag name='qom-list-properties'/> + <flag name='memory-backend-file.discard-data'/> + <flag name='sdl-gl'/> + <flag name='screendump_device'/> + <flag name='hda-output'/> + <flag name='blockdev-del'/> + <flag name='vmgenid'/> + <flag name='vhost-vsock'/> + <flag name='chardev-fd-pass'/> + <flag name='tpm-emulator'/> + <flag name='mch'/> + <flag name='mch.extended-tseg-mbytes'/> + <flag name='usb-storage.werror'/> + <flag name='egl-headless'/> + <flag name='vfio-pci.display'/> + <flag name='blockdev'/> + <flag name='memory-backend-memfd'/> + <flag name='memory-backend-memfd.hugetlb'/> + <flag name='iothread.poll-max-ns'/> + <flag name='egl-headless.rendernode'/> + <flag name='incremental-backup'/> + </qemuCaps> + <devices> + <device alias='rng0'/> + <device alias='sound0-codec0'/> + <device alias='virtio-disk0'/> + <device alias='virtio-serial0'/> + <device alias='video0'/> + <device alias='serial0'/> + <device alias='sound0'/> + <device alias='channel1'/> + <device alias='channel0'/> + <device alias='usb'/> + </devices> + <libDir path='/var/lib/libvirt/qemu/domain-4-copy'/> + <channelTargetDir path='/var/lib/libvirt/qemu/channel/target/domain-4-copy'/> + <chardevStdioLogd/> + <allowReboot value='yes'/> + <nodename index='0'/> + <blockjobs active='yes'> + <blockjob name='backup-vda-libvirt-3-format' type='backup' state='running'> + <disk dst='vda'/> + <bitmap name='bitmapname'/> + <backup id='1'/> + <store type='file' format='qcow2'> + <source file='/path/to/file' index='1337'> + <privateData> + <nodenames> + <nodename type='storage' name='libvirt-1337-storage'/> + <nodename type='format' name='libvirt-1337-format'/> + </nodenames> + </privateData> + </source> + </store> + </blockjob> + </blockjobs> + <backups nextid='2'> + <domainbackup mode='pull' id='1'> + <incremental>12345</incremental> + <server transport='tcp' name='localhost' port='10809'/> + <disks> + <disk name='vda' backup='yes' state='running' type='file'> + <scratch file='/path/to/file/'/> + </disk> + </disks> + </domainbackup> + </backups> + <domain type='kvm' id='4'> + <name>copy</name> + <uuid>0439a4a8-db56-4933-9183-d8681d7b0746</uuid> + <memory unit='KiB'>1024000</memory> + <currentMemory unit='KiB'>1024000</currentMemory> + <vcpu placement='static'>1</vcpu> + <resource> + <partition>/machine</partition> + </resource> + <os> + <type arch='x86_64' machine='pc-i440fx-2.9'>hvm</type> + <boot dev='hd'/> + <bootmenu enable='yes'/> + </os> + <features> + <acpi/> + <apic/> + <vmport state='off'/> + </features> + <clock offset='utc'> + <timer name='rtc' tickpolicy='catchup'/> + <timer name='pit' tickpolicy='delay'/> + <timer name='hpet' present='no'/> + </clock> + <on_poweroff>destroy</on_poweroff> + <on_reboot>restart</on_reboot> + <on_crash>restart</on_crash> + <pm> + <suspend-to-mem enabled='no'/> + <suspend-to-disk enabled='no'/> + </pm> + <devices> + <emulator>/usr/bin/qemu-system-x86_64</emulator> + <disk type='file' device='disk'> + <driver name='qemu' type='qcow2'/> + <source file='/tmp/pull4.qcow2' index='3'> + <privateData> + <nodenames> + <nodename type='storage' name='libvirt-3-storage'/> + <nodename type='format' name='libvirt-3-format'/> + </nodenames> + </privateData> + </source> + <backingStore type='file' index='13'> + <format type='qcow2'/> + <source file='/tmp/pull3.qcow2'> + <privateData> + <nodenames> + <nodename type='storage' name='libvirt-13-storage'/> + <nodename type='format' name='libvirt-13-format'/> + </nodenames> + <relPath>pull3.qcow2</relPath> + </privateData> + </source> + <backingStore type='file' index='14'> + <format type='qcow2'/> + <source file='/tmp/pull2.qcow2'> + <privateData> + <nodenames> + <nodename type='storage' name='libvirt-14-storage'/> + <nodename type='format' name='libvirt-14-format'/> + </nodenames> + <relPath>pull2.qcow2</relPath> + </privateData> + </source> + <backingStore type='file' index='15'> + <format type='qcow2'/> + <source file='/tmp/pull1.qcow2'> + <privateData> + <nodenames> + <nodename type='storage' name='libvirt-15-storage'/> + <nodename type='format' name='libvirt-15-format'/> + </nodenames> + <relPath>pull1.qcow2</relPath> + </privateData> + </source> + <backingStore type='file' index='16'> + <format type='qcow2'/> + <source file='/tmp/pull0.qcow2'> + <privateData> + <nodenames> + <nodename type='storage' name='libvirt-16-storage'/> + <nodename type='format' name='libvirt-16-format'/> + </nodenames> + <relPath>pull0.qcow2</relPath> + </privateData> + </source> + <backingStore/> + </backingStore> + </backingStore> + </backingStore> + </backingStore> + <target dev='vda' bus='virtio'/> + <alias name='virtio-disk1'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x0b' function='0x0'/> + <privateData> + <qom name='/machine/peripheral/virtio-disk1/virtio-backend'/> + </privateData> + </disk> + <disk type='file' device='disk'> + <driver name='qemu' type='qcow2'/> + <source file='/tmp/commit4.qcow2' index='2'> + <privateData> + <nodenames> + <nodename type='storage' name='libvirt-2-storage'/> + <nodename type='format' name='libvirt-2-format'/> + </nodenames> + </privateData> + </source> + <backingStore type='file' index='9'> + <format type='qcow2'/> + <source file='/tmp/commit3.qcow2'> + <privateData> + <nodenames> + <nodename type='storage' name='libvirt-9-storage'/> + <nodename type='format' name='libvirt-9-format'/> + </nodenames> + <relPath>commit3.qcow2</relPath> + </privateData> + </source> + <backingStore type='file' index='10'> + <format type='qcow2'/> + <source file='/tmp/commit2.qcow2'> + <privateData> + <nodenames> + <nodename type='storage' name='libvirt-10-storage'/> + <nodename type='format' name='libvirt-10-format'/> + </nodenames> + <relPath>commit2.qcow2</relPath> + </privateData> + </source> + <backingStore type='file' index='11'> + <format type='qcow2'/> + <source file='/tmp/commit1.qcow2'> + <privateData> + <nodenames> + <nodename type='storage' name='libvirt-11-storage'/> + <nodename type='format' name='libvirt-11-format'/> + </nodenames> + <relPath>commit1.qcow2</relPath> + </privateData> + </source> + <backingStore type='file' index='12'> + <format type='qcow2'/> + <source file='/tmp/commit0.qcow2'> + <privateData> + <nodenames> + <nodename type='storage' name='libvirt-12-storage'/> + <nodename type='format' name='libvirt-12-format'/> + </nodenames> + <relPath>commit0.qcow2</relPath> + </privateData> + </source> + <backingStore/> + </backingStore> + </backingStore> + </backingStore> + </backingStore> + <target dev='vdc' bus='virtio'/> + <alias name='virtio-disk2'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x0c' function='0x0'/> + <privateData> + <qom name='/machine/peripheral/virtio-disk2/virtio-backend'/> + </privateData> + </disk> + <disk type='file' device='disk'> + <driver name='qemu' type='qcow2'/> + <source file='/tmp/copy4.qcow2' index='1'> + <privateData> + <nodenames> + <nodename type='storage' name='libvirt-1-storage'/> + <nodename type='format' name='libvirt-1-format'/> + </nodenames> + </privateData> + </source> + <backingStore type='file' index='5'> + <format type='qcow2'/> + <source file='/tmp/copy3.qcow2'> + <privateData> + <nodenames> + <nodename type='storage' name='libvirt-5-storage'/> + <nodename type='format' name='libvirt-5-format'/> + </nodenames> + <relPath>copy3.qcow2</relPath> + </privateData> + </source> + <backingStore type='file' index='6'> + <format type='qcow2'/> + <source file='/tmp/copy2.qcow2'> + <privateData> + <nodenames> + <nodename type='storage' name='libvirt-6-storage'/> + <nodename type='format' name='libvirt-6-format'/> + </nodenames> + <relPath>copy2.qcow2</relPath> + </privateData> + </source> + <backingStore type='file' index='7'> + <format type='qcow2'/> + <source file='/tmp/copy1.qcow2'> + <privateData> + <nodenames> + <nodename type='storage' name='libvirt-7-storage'/> + <nodename type='format' name='libvirt-7-format'/> + </nodenames> + <relPath>copy1.qcow2</relPath> + </privateData> + </source> + <backingStore type='file' index='8'> + <format type='qcow2'/> + <source file='/tmp/copy0.qcow2'> + <privateData> + <nodenames> + <nodename type='storage' name='libvirt-8-storage'/> + <nodename type='format' name='libvirt-8-format'/> + </nodenames> + <relPath>copy0.qcow2</relPath> + </privateData> + </source> + <backingStore/> + </backingStore> + </backingStore> + </backingStore> + </backingStore> + <target dev='vdd' bus='virtio'/> + <alias name='virtio-disk3'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x0d' function='0x0'/> + <privateData> + <qom name='/machine/peripheral/virtio-disk3/virtio-backend'/> + </privateData> + </disk> + <disk type='file' device='disk'> + <driver name='qemu' type='qcow2'/> + <source file='/tmp/activecommit4.qcow2' index='17'> + <privateData> + <nodenames> + <nodename type='storage' name='libvirt-17-storage'/> + <nodename type='format' name='libvirt-17-format'/> + </nodenames> + </privateData> + </source> + <backingStore type='file' index='18'> + <format type='qcow2'/> + <source file='/tmp/activecommit3.qcow2'> + <privateData> + <nodenames> + <nodename type='storage' name='libvirt-18-storage'/> + <nodename type='format' name='libvirt-18-format'/> + </nodenames> + <relPath>activecommit3.qcow2</relPath> + </privateData> + </source> + <backingStore type='file' index='19'> + <format type='qcow2'/> + <source file='/tmp/activecommit2.qcow2'> + <privateData> + <nodenames> + <nodename type='storage' name='libvirt-19-storage'/> + <nodename type='format' name='libvirt-19-format'/> + </nodenames> + <relPath>activecommit2.qcow2</relPath> + </privateData> + </source> + <backingStore type='file' index='20'> + <format type='qcow2'/> + <source file='/tmp/activecommit1.qcow2'> + <privateData> + <nodenames> + <nodename type='storage' name='libvirt-20-storage'/> + <nodename type='format' name='libvirt-20-format'/> + </nodenames> + <relPath>activecommit1.qcow2</relPath> + </privateData> + </source> + <backingStore type='file' index='21'> + <format type='qcow2'/> + <source file='/tmp/activecommit0.qcow2'> + <privateData> + <nodenames> + <nodename type='storage' name='libvirt-21-storage'/> + <nodename type='format' name='libvirt-21-format'/> + </nodenames> + <relPath>activecommit0.qcow2</relPath> + </privateData> + </source> + <backingStore/> + </backingStore> + </backingStore> + </backingStore> + </backingStore> + <target dev='vde' bus='virtio'/> + <alias name='virtio-disk3'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x0e' function='0x0'/> + <privateData> + <qom name='/machine/peripheral/virtio-disk3/virtio-backend'/> + </privateData> + </disk> + <controller type='usb' index='0' model='piix3-uhci'> + <alias name='usb'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'/> + </controller> + <controller type='pci' index='0' model='pci-root'> + <alias name='pci.0'/> + </controller> + <serial type='pty'> + <source path='/dev/pts/34'/> + <target type='isa-serial' port='0'> + <model name='isa-serial'/> + </target> + <alias name='serial0'/> + </serial> + <console type='pty' tty='/dev/pts/34'> + <source path='/dev/pts/34'/> + <target type='serial' port='0'/> + <alias name='serial0'/> + </console> + <input type='mouse' bus='ps2'> + <alias name='input0'/> + </input> + <input type='keyboard' bus='ps2'> + <alias name='input1'/> + </input> + <graphics type='spice' port='5900' autoport='yes' listen='127.0.0.1'> + <listen type='address' address='127.0.0.1' fromConfig='1' autoGenerated='no'/> + <image compression='off'/> + </graphics> + <video> + <model type='qxl' ram='65536' vram='65536' vgamem='16384' heads='1' primary='yes'/> + <alias name='video0'/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/> + </video> + <memballoon model='none'/> + </devices> + <seclabel type='dynamic' model='selinux' relabel='yes'> + <label>unconfined_u:unconfined_r:svirt_t:s0:c550,c786</label> + <imagelabel>unconfined_u:object_r:svirt_image_t:s0:c550,c786</imagelabel> + </seclabel> + <seclabel type='dynamic' model='dac' relabel='yes'> + <label>+0:+0</label> + <imagelabel>+0:+0</imagelabel> + </seclabel> + </domain> +</domstatus> diff --git a/tests/qemustatusxml2xmldata/backup-pull-out.xml b/tests/qemustatusxml2xmldata/backup-pull-out.xml new file mode 120000 index 0000000000..b706ee2924 --- /dev/null +++ b/tests/qemustatusxml2xmldata/backup-pull-out.xml @@ -0,0 +1 @@ +backup-pull-in.xml \ No newline at end of file diff --git a/tests/qemuxml2xmltest.c b/tests/qemuxml2xmltest.c index 4c7ba98367..be99aaa3a8 100644 --- a/tests/qemuxml2xmltest.c +++ b/tests/qemuxml2xmltest.c @@ -1310,6 +1310,8 @@ mymain(void) DO_TEST_STATUS("blockjob-blockdev"); + DO_TEST_STATUS("backup-pull"); + DO_TEST("vhost-vsock", QEMU_CAPS_DEVICE_VHOST_VSOCK); DO_TEST("vhost-vsock-auto", QEMU_CAPS_DEVICE_VHOST_VSOCK); DO_TEST("vhost-vsock-ccw", QEMU_CAPS_DEVICE_VHOST_VSOCK, -- 2.21.0

On 10/18/19 11:11 AM, Peter Krempa wrote:
Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- .../qemustatusxml2xmldata/backup-pull-in.xml | 607 ++++++++++++++++++ .../qemustatusxml2xmldata/backup-pull-out.xml | 1 + tests/qemuxml2xmltest.c | 2 + 3 files changed, 610 insertions(+) create mode 100644 tests/qemustatusxml2xmldata/backup-pull-in.xml create mode 120000 tests/qemustatusxml2xmldata/backup-pull-out.xml
+ <flag name='incremental-backup'/> + </qemuCaps>
+ <blockjobs active='yes'> + <blockjob name='backup-vda-libvirt-3-format' type='backup' state='running'> + <disk dst='vda'/> + <bitmap name='bitmapname'/> + <backup id='1'/> + <store type='file' format='qcow2'> + <source file='/path/to/file' index='1337'> + <privateData> + <nodenames> + <nodename type='storage' name='libvirt-1337-storage'/> + <nodename type='format' name='libvirt-1337-format'/> + </nodenames> + </privateData> + </source> + </store> + </blockjob> + </blockjobs> + <backups nextid='2'> + <domainbackup mode='pull' id='1'> + <incremental>12345</incremental> + <server transport='tcp' name='localhost' port='10809'/> + <disks> + <disk name='vda' backup='yes' state='running' type='file'> + <scratch file='/path/to/file/'/> + </disk> + </disks> + </domainbackup> + </backups>
Looks good.
diff --git a/tests/qemustatusxml2xmldata/backup-pull-out.xml b/tests/qemustatusxml2xmldata/backup-pull-out.xml new file mode 120000 index 0000000000..b706ee2924 --- /dev/null +++ b/tests/qemustatusxml2xmldata/backup-pull-out.xml @@ -0,0 +1 @@ +backup-pull-in.xml \ No newline at end of file
Do we have to create .out symlinks when the .in is otherwise useful as-is? But you've done more with testsuite cleanups than me, so I trust you on this one. Reviewed-by: Eric Blake <eblake@redhat.com> -- Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3226 Virtualization: qemu.org | libvirt.org

This allows to start and manage the backup job. Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/Makefile.inc.am | 2 + src/qemu/qemu_backup.c | 895 +++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_backup.h | 41 ++ src/qemu/qemu_driver.c | 71 ++++ 4 files changed, 1009 insertions(+) create mode 100644 src/qemu/qemu_backup.c create mode 100644 src/qemu/qemu_backup.h diff --git a/src/qemu/Makefile.inc.am b/src/qemu/Makefile.inc.am index e66da76c0a..af176804a8 100644 --- a/src/qemu/Makefile.inc.am +++ b/src/qemu/Makefile.inc.am @@ -70,6 +70,8 @@ QEMU_DRIVER_SOURCES = \ qemu/qemu_vhost_user_gpu.h \ qemu/qemu_checkpoint.c \ qemu/qemu_checkpoint.h \ + qemu/qemu_backup.c \ + qemu/qemu_backup.h \ $(NULL) diff --git a/src/qemu/qemu_backup.c b/src/qemu/qemu_backup.c new file mode 100644 index 0000000000..c689f870ba --- /dev/null +++ b/src/qemu/qemu_backup.c @@ -0,0 +1,895 @@ +/* + * qemu_backup.c: Implementation and handling of the backup jobs + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <http://www.gnu.org/licenses/>. + */ + +#include <config.h> + +#include "qemu_block.h" +#include "qemu_conf.h" +#include "qemu_capabilities.h" +#include "qemu_monitor.h" +#include "qemu_process.h" +#include "qemu_backup.h" +#include "qemu_monitor_json.h" +#include "qemu_checkpoint.h" +#include "qemu_command.h" + +#include "virerror.h" +#include "virlog.h" +#include "virbuffer.h" +#include "viralloc.h" +#include "virxml.h" +#include "virstoragefile.h" +#include "virstring.h" +#include "backup_conf.h" +#include "virdomaincheckpointobjlist.h" + +#define VIR_FROM_THIS VIR_FROM_QEMU + +VIR_LOG_INIT("qemu.qemu_backup"); + + +static unsigned long long +qemuDomainGetBackupNextId(virDomainObjPtr vm) +{ + qemuDomainObjPrivatePtr priv = vm->privateData; + + return priv->backupnextid++; +} + + +static virDomainBackupDefPtr +qemuDomainGetBackup(virDomainObjPtr vm, + int id) +{ + qemuDomainObjPrivatePtr priv = vm->privateData; + + if (!priv->backup || + (id != 0 && id != priv->backup->id)) { + virReportError(VIR_ERR_NO_DOMAIN_BACKUP, + _("no domain backup job with id '%d'"), id); + return NULL; + } + + return priv->backup; +} + + +static int +qemuBackupPrepare(virDomainObjPtr vm, + virDomainBackupDefPtr def) +{ + /* we support just one job but we number them appropriately */ + def->id = qemuDomainGetBackupNextId(vm); + + if (def->type == VIR_DOMAIN_BACKUP_TYPE_PULL) { + if (!def->server) { + def->server = g_new(virStorageNetHostDef, 1); + + def->server->transport = VIR_STORAGE_NET_HOST_TRANS_TCP; + def->server->name = g_strdup("localhost"); + } + + switch ((virStorageNetHostTransport) def->server->transport) { + case VIR_STORAGE_NET_HOST_TRANS_TCP: + /* TODO: Update qemu.conf to provide a port range, + * probably starting at 10809, for obtaining automatic + * port via virPortAllocatorAcquire, as well as store + * somewhere if we need to call virPortAllocatorRelease + * during BackupEnd. Until then, user must provide port */ + if (!def->server->port) { + virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", + _("<domainbackup> must specify TCP port for now")); + return -1; + } + break; + + case VIR_STORAGE_NET_HOST_TRANS_UNIX: + /* TODO: Do we need to mess with selinux? */ + break; + + case VIR_STORAGE_NET_HOST_TRANS_RDMA: + case VIR_STORAGE_NET_HOST_TRANS_LAST: + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("unexpected transport in <domainbackup>")); + return -1; + } + } + + return 0; +} + + +struct qemuBackupDiskData { + virDomainBackupDiskDefPtr backupdisk; + virDomainDiskDefPtr domdisk; + qemuBlockJobDataPtr blockjob; + virStorageSourcePtr store; + char *incrementalBitmap; + qemuBlockStorageSourceChainDataPtr crdata; + bool labelled; + bool initialized; + bool created; + bool added; + bool started; + bool done; +}; + + +static void +qemuBackupDiskDataCleanupOne(virDomainObjPtr vm, + struct qemuBackupDiskData *dd) +{ + qemuDomainObjPrivatePtr priv = vm->privateData; + + if (dd->started) + return; + + if (dd->added) { + qemuDomainObjEnterMonitor(priv->driver, vm); + qemuBlockStorageSourceAttachRollback(priv->mon, dd->crdata->srcdata[0]); + ignore_value(qemuDomainObjExitMonitor(priv->driver, vm)); + } + + if (dd->created) { + if (virStorageFileUnlink(dd->store) < 0) + VIR_WARN("Unable to remove just-created %s", NULLSTR(dd->store->path)); + } + + if (dd->initialized) + virStorageFileDeinit(dd->store); + + if (dd->labelled) + qemuDomainStorageSourceAccessRevoke(priv->driver, vm, dd->store); + + if (dd->blockjob) + qemuBlockJobStartupFinalize(vm, dd->blockjob); + + qemuBlockStorageSourceChainDataFree(dd->crdata); +} + + +static void +qemuBackupDiskDataCleanup(virDomainObjPtr vm, + struct qemuBackupDiskData *dd, + size_t ndd) +{ + virErrorPtr orig_err; + size_t i; + + if (!dd) + return; + + virErrorPreserveLast(&orig_err); + + for (i = 0; i < ndd; i++) + qemuBackupDiskDataCleanupOne(vm, dd + i); + + g_free(dd); + virErrorRestore(&orig_err); +} + + + +static int +qemuBackupDiskPrepareOneBitmaps(struct qemuBackupDiskData *dd, + virJSONValuePtr actions, + virDomainMomentObjPtr *incremental) +{ + g_autoptr(virJSONValue) mergebitmapsdisk = NULL; + g_autoptr(virJSONValue) mergebitmapsstore = NULL; + + if (!(mergebitmapsdisk = virJSONValueNewArray())) + return -1; + + if (!(mergebitmapsstore = virJSONValueNewArray())) + return -1; + + /* TODO: this code works only if the bitmaps are present on a single node. + * The algorithm needs to be changed so that it looks into the backing chain + * so that we can combine all relevant bitmaps for a given backing chain */ + while (*incremental) { + if (qemuMonitorTransactionBitmapMergeSourceAddBitmap(mergebitmapsdisk, + dd->domdisk->src->nodeformat, + (*incremental)->def->name) < 0) + return -1; + + if (qemuMonitorTransactionBitmapMergeSourceAddBitmap(mergebitmapsstore, + dd->domdisk->src->nodeformat, + (*incremental)->def->name) < 0) + return -1; + + incremental++; + } + + if (qemuMonitorTransactionBitmapAdd(actions, + dd->domdisk->src->nodeformat, + dd->incrementalBitmap, + false, + true) < 0) + return -1; + + if (qemuMonitorTransactionBitmapMerge(actions, + dd->domdisk->src->nodeformat, + dd->incrementalBitmap, + &mergebitmapsdisk) < 0) + return -1; + + if (qemuMonitorTransactionBitmapAdd(actions, + dd->store->nodeformat, + dd->incrementalBitmap, + false, + true) < 0) + return -1; + + if (qemuMonitorTransactionBitmapMerge(actions, + dd->store->nodeformat, + dd->incrementalBitmap, + &mergebitmapsstore) < 0) + return -1; + + + return 0; +} + + +static int +qemuBackupDiskPrepareDataOne(virDomainObjPtr vm, + virDomainBackupDiskDefPtr backupdisk, + struct qemuBackupDiskData *dd, + virJSONValuePtr actions, + virDomainMomentObjPtr *incremental, + virQEMUDriverConfigPtr cfg, + unsigned int jobid) +{ + qemuDomainObjPrivatePtr priv = vm->privateData; + + /* set data structure */ + dd->backupdisk = backupdisk; + dd->store = dd->backupdisk->store; + + if (!(dd->domdisk = virDomainDiskByTarget(vm->def, dd->backupdisk->name))) { + virReportError(VIR_ERR_INVALID_ARG, + _("no disk named '%s'"), dd->backupdisk->name); + return -1; + } + + if (!dd->store->format) + dd->store->format = VIR_STORAGE_FILE_QCOW2; + + if (qemuDomainStorageFileInit(priv->driver, vm, dd->store, dd->domdisk->src) < 0) + return -1; + + if (qemuDomainPrepareStorageSourceBlockdev(NULL, dd->store, priv, cfg) < 0) + return -1; + + if (incremental) { + dd->incrementalBitmap = g_strdup_printf("backup-%u-%s", jobid, dd->domdisk->dst); + + if (qemuBackupDiskPrepareOneBitmaps(dd, actions, incremental) < 0) + return -1; + } + + if (!(dd->blockjob = qemuBlockJobDiskNewBackup(vm, dd->domdisk, dd->store, + dd->incrementalBitmap, + jobid))) + return -1; + + if (!(dd->crdata = qemuBuildStorageSourceChainAttachPrepareBlockdevTop(dd->store, + NULL, + priv->qemuCaps))) + return -1; + + return 0; +} + + +static int +qemuBackupDiskPrepareDataOnePush(virJSONValuePtr actions, + struct qemuBackupDiskData *dd) +{ + qemuMonitorTransactionBackupSyncMode syncmode = QEMU_MONITOR_TRANSACTION_BACKUP_SYNC_MODE_FULL; + + if (dd->incrementalBitmap) + syncmode = QEMU_MONITOR_TRANSACTION_BACKUP_SYNC_MODE_INCREMENTAL; + + if (qemuMonitorTransactionBackup(actions, + dd->domdisk->src->nodeformat, + dd->blockjob->name, + dd->store->nodeformat, + dd->incrementalBitmap, + syncmode) < 0) + return -1; + + return 0; +} + + +static int +qemuBackupDiskPrepareDataOnePull(virJSONValuePtr actions, + struct qemuBackupDiskData *dd) +{ + if (qemuMonitorTransactionBackup(actions, + dd->domdisk->src->nodeformat, + dd->blockjob->name, + dd->store->nodeformat, + NULL, + QEMU_MONITOR_TRANSACTION_BACKUP_SYNC_MODE_NONE) < 0) + return -1; + + return 0; +} + + +static ssize_t +qemuBackupDiskPrepareData(virDomainObjPtr vm, + virDomainBackupDefPtr def, + virDomainMomentObjPtr *incremental, + virJSONValuePtr actions, + virQEMUDriverConfigPtr cfg, + struct qemuBackupDiskData **rdd) +{ + struct qemuBackupDiskData *disks = NULL; + ssize_t ndisks = 0; + size_t i; + + disks = g_new0(struct qemuBackupDiskData, def->ndisks); + + for (i = 0; i < def->ndisks; i++) { + virDomainBackupDiskDef *backupdisk = &def->disks[i]; + struct qemuBackupDiskData *dd = disks + ndisks; + + if (!backupdisk->store) + continue; + + ndisks++; + + if (qemuBackupDiskPrepareDataOne(vm, backupdisk, dd, actions, + incremental, cfg, def->id) < 0) + goto error; + + if (def->type == VIR_DOMAIN_BACKUP_TYPE_PULL) { + if (qemuBackupDiskPrepareDataOnePull(actions, dd) < 0) + goto error; + } else { + if (qemuBackupDiskPrepareDataOnePush(actions, dd) < 0) + goto error; + } + } + + *rdd = g_steal_pointer(&disks); + + return ndisks; + + error: + qemuBackupDiskDataCleanup(vm, disks, ndisks); + return -1; +} + + +static int +qemuBackupDiskPrepareOneStorage(virDomainObjPtr vm, + virHashTablePtr blockNamedNodeData, + struct qemuBackupDiskData *dd) +{ + qemuDomainObjPrivatePtr priv = vm->privateData; + + if (virStorageSourceIsLocalStorage(dd->store) && + !virFileExists(dd->store->path) && + virStorageFileSupportsCreate(dd->store)) { + + if (qemuDomainStorageFileInit(priv->driver, vm, dd->store, NULL) < 0) + return -1; + + dd->initialized = true; + + if (virStorageFileCreate(dd->store) < 0) { + virReportSystemError(errno, + _("failed to create image file '%s'"), + NULLSTR(dd->store->path)); + return -1; + } + + dd->created = true; + } + + if (qemuDomainStorageSourceAccessAllow(priv->driver, vm, dd->store, false, + true) < 0) + return -1; + + dd->labelled = true; + + if (qemuBlockStorageSourceCreateDetectSize(blockNamedNodeData, + dd->store, dd->domdisk->src) < 0) + return -1; + + if (qemuBlockStorageSourceCreate(vm, dd->store, NULL, NULL, + dd->crdata->srcdata[0], QEMU_ASYNC_JOB_NONE) < 0) + return -1; + + dd->added = true; + + return 0; +} + + +static int +qemuBackupDiskPrepareStorage(virDomainObjPtr vm, + struct qemuBackupDiskData *disks, + size_t ndisks, + virHashTablePtr blockNamedNodeData) +{ + size_t i; + + for (i = 0; i < ndisks; i++) { + if (qemuBackupDiskPrepareOneStorage(vm, blockNamedNodeData, disks + i) < 0) + return -1; + } + + return 0; +} + + +static void +qemuBackupDiskStarted(virDomainObjPtr vm, + struct qemuBackupDiskData *dd, + size_t ndd) +{ + size_t i; + + for (i = 0; i < ndd; i++) { + dd[i].started = true; + dd[i].backupdisk->state = VIR_DOMAIN_BACKUP_DISK_STATE_RUNNING; + qemuBlockJobStarted(dd->blockjob, vm); + } +} + + +/** + * qemuBackupBeginPullExportDisks: + * @vm: domain object + * @disks: backup disk data list + * @ndisks: number of valid disks in @disks + * + * Exports all disks from @dd when doing a pull backup in the NBD server. This + * function must be called while in the monitor context. + */ +static int +qemuBackupBeginPullExportDisks(virDomainObjPtr vm, + struct qemuBackupDiskData *disks, + size_t ndisks) +{ + qemuDomainObjPrivatePtr priv = vm->privateData; + size_t i; + + for (i = 0; i < ndisks; i++) { + struct qemuBackupDiskData *dd = disks + i; + + if (qemuMonitorNBDServerAdd(priv->mon, + dd->store->nodeformat, + dd->domdisk->dst, + false, + dd->incrementalBitmap) < 0) + return -1; + } + + return 0; +} + + +/** + * qemuBackupBeginCollectIncrementalCheckpoints: + * @vm: domain object + * @incrFrom: name of checkpoint representing starting point of incremental backup + * + * Returns a NULL terminated list of pointers to checkpoints in chronological + * order starting from the 'current' checkpoint until reaching @incrFrom. + */ +static virDomainMomentObjPtr * +qemuBackupBeginCollectIncrementalCheckpoints(virDomainObjPtr vm, + const char *incrFrom) +{ + virDomainMomentObjPtr n = virDomainCheckpointGetCurrent(vm->checkpoints); + g_autofree virDomainMomentObjPtr *incr = NULL; + size_t nincr = 0; + + while (n) { + if (VIR_APPEND_ELEMENT_COPY(incr, nincr, n) < 0) + return NULL; + + if (STREQ(n->def->name, incrFrom)) { + virDomainMomentObjPtr terminator = NULL; + if (VIR_APPEND_ELEMENT_COPY(incr, nincr, terminator) < 0) + return NULL; + + return g_steal_pointer(&incr); + } + + if (!n->def->parent_name) + break; + + n = virDomainCheckpointFindByName(vm->checkpoints, n->def->parent_name); + } + + virReportError(VIR_ERR_OPERATION_INVALID, + _("could not locate checkpoint '%s' for incremental backup"), + incrFrom); + return NULL; +} + + +static void +qemuBackupJobCancelBlockjobs(virDomainObjPtr vm, + virDomainBackupDefPtr backup) +{ + qemuDomainObjPrivatePtr priv = vm->privateData; + size_t i; + int rc = 0; + + for (i = 0; i < backup->ndisks; i++) { + virDomainBackupDiskDefPtr backupdisk = backup->disks + i; + virDomainDiskDefPtr disk; + g_autoptr(qemuBlockJobData) job = NULL; + + if (!backupdisk->store || + backupdisk->state != VIR_DOMAIN_BACKUP_DISK_STATE_RUNNING) + continue; + + /* Look up corresponding disk as backupdisk->idx is no longer reliable */ + if (!(disk = virDomainDiskByTarget(vm->def, backupdisk->name))) + continue; + + if (!(job = qemuBlockJobDiskGetJob(disk))) + continue; + + qemuDomainObjEnterMonitor(priv->driver, vm); + + rc = qemuMonitorJobCancel(priv->mon, job->name, false); + + if (qemuDomainObjExitMonitor(priv->driver, vm) < 0) + return; + + if (rc < 0) + backupdisk->state = VIR_DOMAIN_BACKUP_DISK_STATE_FAILED; + else + backupdisk->state = VIR_DOMAIN_BACKUP_DISK_STATE_CANCELLING; + + } +} + + +int +qemuBackupBegin(virDomainObjPtr vm, + const char *backupXML, + const char *checkpointXML, + unsigned int flags) +{ + qemuDomainObjPrivatePtr priv = vm->privateData; + g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(priv->driver); + virDomainBackupDefPtr def = NULL; + g_autoptr(virCaps) caps = NULL; + g_autofree char *suffix = NULL; + struct timeval tv; + bool pull = false; + virDomainMomentObjPtr chk = NULL; + g_autoptr(virDomainCheckpointDef) chkdef = NULL; + g_autofree virDomainMomentObjPtr *incremental = NULL; + g_autoptr(virJSONValue) actions = NULL; + struct qemuBackupDiskData *dd = NULL; + ssize_t ndd = 0; + g_autoptr(virHashTable) blockNamedNodeData = NULL; + bool job_started = false; + bool nbd_running = false; + int rc = 0; + int ret = -1; + + virCheckFlags(0, -1); + + if (!virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_INCREMENTAL_BACKUP)) { + virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", + _("incremental backup is not supported yet")); + return -1; + } + + if (!(caps = virQEMUDriverGetCapabilities(priv->driver, false))) + goto cleanup; + + if (!(def = virDomainBackupDefParseString(backupXML, priv->driver->xmlopt, 0))) + goto cleanup; + + if (checkpointXML) { + if (!(chkdef = virDomainCheckpointDefParseString(checkpointXML, caps, + priv->driver->xmlopt, + priv->qemuCaps, 0))) + goto cleanup; + + suffix = g_strdup(chkdef->parent.name); + } else { + gettimeofday(&tv, NULL); + suffix = g_strdup_printf("%lld", (long long)tv.tv_sec); + } + + if (def->type == VIR_DOMAIN_BACKUP_TYPE_PULL) + pull = true; + + /* We are going to modify the domain below. */ + if (qemuDomainObjBeginJob(priv->driver, vm, QEMU_JOB_MODIFY) < 0) + goto cleanup; + + if (!virDomainObjIsActive(vm)) { + virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", + _("cannot perform disk backup for inactive domain")); + goto endjob; + } + + if (priv->backup) { + virReportError(VIR_ERR_OPERATION_INVALID, + "%s", _("another backup job is already running")); + goto endjob; + } + + if (qemuBackupPrepare(vm, def) < 0) + goto endjob; + + if (virDomainBackupAlignDisks(def, vm->def, suffix) < 0) + goto endjob; + + if (def->incremental && + !(incremental = qemuBackupBeginCollectIncrementalCheckpoints(vm, def->incremental))) + goto endjob; + + if (!(actions = virJSONValueNewArray())) + goto endjob; + + if (chkdef) { + if (qemuCheckpointCreateCommon(priv->driver, vm, caps, &chkdef, + &actions, &chk) < 0) + goto endjob; + } + + if ((ndd = qemuBackupDiskPrepareData(vm, def, incremental, actions, cfg, &dd)) <= 0) { + if (ndd == 0) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("no disks selected for backup")); + } + + goto endjob; + } + + qemuDomainObjEnterMonitor(priv->driver, vm); + blockNamedNodeData = qemuMonitorBlockGetNamedNodeData(priv->mon); + if (qemuDomainObjExitMonitor(priv->driver, vm) < 0 || !blockNamedNodeData) + goto endjob; + + if (qemuBackupDiskPrepareStorage(vm, dd, ndd, blockNamedNodeData) < 0) + goto endjob; + + priv->backup = g_steal_pointer(&def); + + qemuDomainObjEnterMonitor(priv->driver, vm); + + /* TODO: TLS is a must-have for the modern age */ + if (pull) { + if ((rc = qemuMonitorNBDServerStart(priv->mon, priv->backup->server, NULL)) == 0) + nbd_running = true; + } + + if (rc == 0) + rc = qemuMonitorTransaction(priv->mon, &actions); + + if (qemuDomainObjExitMonitor(priv->driver, vm) < 0 || rc < 0) + goto endjob; + + job_started = true; + qemuBackupDiskStarted(vm, dd, ndd); + + if (chk && + qemuCheckpointCreateFinalize(priv->driver, vm, cfg, chk, true) < 0) + goto endjob; + + if (pull) { + qemuDomainObjEnterMonitor(priv->driver, vm); + /* note that if the export fails we've already created the checkpoint + * and we will not delete it */ + rc = qemuBackupBeginPullExportDisks(vm, dd, ndd); + if (qemuDomainObjExitMonitor(priv->driver, vm) < 0) + goto endjob; + + if (rc < 0) { + qemuBackupJobCancelBlockjobs(vm, priv->backup); + goto endjob; + } + } + + ret = priv->backup->id; + + endjob: + qemuBackupDiskDataCleanup(vm, dd, ndd); + if (!job_started && nbd_running) { + qemuDomainObjEnterMonitor(priv->driver, vm); + ignore_value(qemuMonitorNBDServerStop(priv->mon)); + ignore_value(qemuDomainObjExitMonitor(priv->driver, vm)); + } + + if (ret < 0 && !job_started) + def = g_steal_pointer(&priv->backup); + + qemuDomainObjEndJob(priv->driver, vm); + + cleanup: + virDomainBackupDefFree(def); + return ret; +} + + +char * +qemuBackupGetXMLDesc(virDomainObjPtr vm, + int id, + unsigned int flags) +{ + g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER; + virDomainBackupDefPtr backup; + + virCheckFlags(0, NULL); + + if (!(backup = qemuDomainGetBackup(vm, id))) + return NULL; + + if (virDomainBackupDefFormat(&buf, backup, false) < 0) + return NULL; + + if (virBufferCheckError(&buf) < 0) + return NULL; + + return virBufferContentAndReset(&buf); +} + + +static void +qemuBackupJobTerminate(virDomainObjPtr vm, + virDomainBackupDefPtr backup, + bool success G_GNUC_UNUSED) + +{ + qemuDomainObjPrivatePtr priv = vm->privateData; + + VIR_DEBUG("id:'%d'", backup->id); + + if (priv->backup == backup) + priv->backup = NULL; + + virDomainBackupDefFree(backup); +} + + +void +qemuBackupNotifyBlockjobEnd(virDomainObjPtr vm, + int id, + virDomainDiskDefPtr disk, + qemuBlockjobState state) +{ + qemuDomainObjPrivatePtr priv = vm->privateData; + bool all_complete = true; + bool has_running = false; + bool has_cancelling = false; + virDomainBackupDefPtr backup; + size_t i; + + VIR_DEBUG("vm: '%s', id:'%d', disk:'%s', state:'%d'", + vm->def->name, id, disk->dst, state); + + if (!(backup = qemuDomainGetBackup(vm, id))) + return; + + if (backup->type == VIR_DOMAIN_BACKUP_TYPE_PULL) { + qemuDomainObjEnterMonitor(priv->driver, vm); + ignore_value(qemuMonitorNBDServerStop(priv->mon)); + if (qemuDomainObjExitMonitor(priv->driver, vm) < 0) + return; + } + + for (i = 0; i < backup->ndisks; i++) { + virDomainBackupDiskDefPtr backupdisk = backup->disks + i; + + if (!backupdisk->store) + continue; + + if (STREQ(disk->dst, backupdisk->name)) { + switch (state) { + case QEMU_BLOCKJOB_STATE_COMPLETED: + backupdisk->state = VIR_DOMAIN_BACKUP_DISK_STATE_COMPLETE; + break; + + case QEMU_BLOCKJOB_STATE_FAILED: + backupdisk->state = VIR_DOMAIN_BACKUP_DISK_STATE_FAILED; + break; + + case QEMU_BLOCKJOB_STATE_CANCELLED: + backupdisk->state = VIR_DOMAIN_BACKUP_DISK_STATE_CANCELLED; + break; + + case QEMU_BLOCKJOB_STATE_READY: + case QEMU_BLOCKJOB_STATE_NEW: + case QEMU_BLOCKJOB_STATE_RUNNING: + case QEMU_BLOCKJOB_STATE_CONCLUDED: + case QEMU_BLOCKJOB_STATE_ABORTING: + case QEMU_BLOCKJOB_STATE_PIVOTING: + case QEMU_BLOCKJOB_STATE_LAST: + default: + break; + } + } + + switch (backupdisk->state) { + case VIR_DOMAIN_BACKUP_DISK_STATE_COMPLETE: + break; + + case VIR_DOMAIN_BACKUP_DISK_STATE_RUNNING: + all_complete = false; + has_running = true; + break; + + case VIR_DOMAIN_BACKUP_DISK_STATE_CANCELLING: + all_complete = false; + has_cancelling = true; + break; + + case VIR_DOMAIN_BACKUP_DISK_STATE_CANCELLED: + case VIR_DOMAIN_BACKUP_DISK_STATE_FAILED: + all_complete = false; + break; + + case VIR_DOMAIN_BACKUP_DISK_STATE_NONE: + case VIR_DOMAIN_BACKUP_DISK_STATE_LAST: + break; + } + } + + if (!has_running && !has_cancelling) { + qemuBackupJobTerminate(vm, backup, all_complete); + } else if (has_running) { + qemuBackupJobCancelBlockjobs(vm, backup); + } +} + + +int +qemuBackupEnd(virDomainObjPtr vm, + int id, + unsigned int flags) +{ + qemuDomainObjPrivatePtr priv = vm->privateData; + virDomainBackupDefPtr backup = NULL; + int ret = -1; + + virCheckFlags(0, -1); + + if (qemuDomainObjBeginJob(priv->driver, vm, QEMU_JOB_MODIFY) < 0) + return -1; + + if (!(backup = qemuDomainGetBackup(vm, id))) + goto endjob; + + qemuBackupJobCancelBlockjobs(vm, backup); + + ret = 0; + + endjob: + qemuDomainObjEndJob(priv->driver, vm); + + return ret; +} diff --git a/src/qemu/qemu_backup.h b/src/qemu/qemu_backup.h new file mode 100644 index 0000000000..e71d8bd72d --- /dev/null +++ b/src/qemu/qemu_backup.h @@ -0,0 +1,41 @@ +/* + * qemu_backup.h: Implementation and handling of the backup jobs + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <http://www.gnu.org/licenses/>. + */ + +#pragma once + +int +qemuBackupBegin(virDomainObjPtr vm, + const char *backupXML, + const char *checkpointXML, + unsigned int flags); + +char * +qemuBackupGetXMLDesc(virDomainObjPtr vm, + int id, + unsigned int flags); + +int +qemuBackupEnd(virDomainObjPtr vm, + int id, + unsigned int flags); + +void +qemuBackupNotifyBlockjobEnd(virDomainObjPtr vm, + int id, + virDomainDiskDefPtr disk, + qemuBlockjobState state); diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 330345b5de..8bf135c614 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -53,6 +53,7 @@ #include "qemu_blockjob.h" #include "qemu_security.h" #include "qemu_checkpoint.h" +#include "qemu_backup.h" #include "virerror.h" #include "virlog.h" @@ -17219,6 +17220,73 @@ qemuDomainCheckpointDelete(virDomainCheckpointPtr checkpoint, } +static int +qemuDomainBackupBegin(virDomainPtr domain, + const char *backupXML, + const char *checkpointXML, + unsigned int flags) +{ + virDomainObjPtr vm = NULL; + int ret = -1; + + if (!(vm = qemuDomainObjFromDomain(domain))) + goto cleanup; + + if (virDomainBackupBeginEnsureACL(domain->conn, vm->def) < 0) + goto cleanup; + + ret = qemuBackupBegin(vm, backupXML, checkpointXML, flags); + + cleanup: + virDomainObjEndAPI(&vm); + return ret; +} + + +static char * +qemuDomainBackupGetXMLDesc(virDomainPtr domain, + int id, + unsigned int flags) +{ + virDomainObjPtr vm = NULL; + char *ret = NULL; + + if (!(vm = qemuDomainObjFromDomain(domain))) + return NULL; + + if (virDomainBackupGetXMLDescEnsureACL(domain->conn, vm->def) < 0) + goto cleanup; + + ret = qemuBackupGetXMLDesc(vm, id, flags); + + cleanup: + virDomainObjEndAPI(&vm); + return ret; +} + + +static int +qemuDomainBackupEnd(virDomainPtr domain, + int id, + unsigned int flags) +{ + virDomainObjPtr vm = NULL; + int ret = -1; + + if (!(vm = qemuDomainObjFromDomain(domain))) + return -1; + + if (virDomainBackupEndEnsureACL(domain->conn, vm->def) < 0) + goto cleanup; + + ret = qemuBackupEnd(vm, id, flags); + + cleanup: + virDomainObjEndAPI(&vm); + return ret; +} + + static int qemuDomainQemuMonitorCommand(virDomainPtr domain, const char *cmd, char **result, unsigned int flags) { @@ -22926,6 +22994,9 @@ static virHypervisorDriver qemuHypervisorDriver = { .domainCheckpointGetParent = qemuDomainCheckpointGetParent, /* 5.6.0 */ .domainCheckpointDelete = qemuDomainCheckpointDelete, /* 5.6.0 */ .domainGetGuestInfo = qemuDomainGetGuestInfo, /* 5.7.0 */ + .domainBackupBegin = qemuDomainBackupBegin, /* 5.9.0 */ + .domainBackupGetXMLDesc = qemuDomainBackupGetXMLDesc, /* 5.9.0 */ + .domainBackupEnd = qemuDomainBackupEnd, /* 5.9.0 */ }; -- 2.21.0

After the individual sub-blockjobs of a backup libvirt job finish we must detect it and notify the parent job, so that it can be properly terminated. Signed-off-by: Peter Krempa <pkrempa@redhat.com> --- src/qemu/qemu_blockjob.c | 45 ++++++++++++++++++++++++++++++++++++ src/qemu/qemu_monitor_json.c | 4 ++++ 2 files changed, 49 insertions(+) diff --git a/src/qemu/qemu_blockjob.c b/src/qemu/qemu_blockjob.c index 6f190b3485..acfc07638b 100644 --- a/src/qemu/qemu_blockjob.c +++ b/src/qemu/qemu_blockjob.c @@ -27,6 +27,7 @@ #include "qemu_block.h" #include "qemu_domain.h" #include "qemu_alias.h" +#include "qemu_backup.h" #include "conf/domain_conf.h" #include "conf/domain_event.h" @@ -1282,6 +1283,49 @@ qemuBlockJobProcessEventConcludedCreate(virQEMUDriverPtr driver, } +static void +qemuBlockJobProcessEventConcludedBackup(virQEMUDriverPtr driver, + virDomainObjPtr vm, + qemuBlockJobDataPtr job, + qemuDomainAsyncJob asyncJob, + qemuBlockjobState newstate) +{ + g_autoptr(qemuBlockStorageSourceAttachData) backend = NULL; + g_autoptr(virJSONValue) actions = NULL; + + qemuBackupNotifyBlockjobEnd(vm, job->data.backup.jobid, job->disk, newstate); + + if (job->data.backup.store && + !(backend = qemuBlockStorageSourceDetachPrepare(job->data.backup.store, NULL))) + return; + + if (job->data.backup.bitmap) { + if (!(actions = virJSONValueNewArray())) + return; + + if (qemuMonitorTransactionBitmapRemove(actions, + job->disk->src->nodeformat, + job->data.backup.bitmap) < 0) + return; + } + + if (qemuDomainObjEnterMonitorAsync(driver, vm, asyncJob) < 0) + return; + + if (backend) + qemuBlockStorageSourceAttachRollback(qemuDomainGetMonitor(vm), backend); + + if (actions) + qemuMonitorTransaction(qemuDomainGetMonitor(vm), &actions); + + if (qemuDomainObjExitMonitor(driver, vm) < 0) + return; + + if (job->data.backup.store) + qemuDomainStorageSourceAccessRevoke(driver, vm, job->data.backup.store); +} + + static void qemuBlockJobEventProcessConcludedTransition(qemuBlockJobDataPtr job, virQEMUDriverPtr driver, @@ -1320,6 +1364,7 @@ qemuBlockJobEventProcessConcludedTransition(qemuBlockJobDataPtr job, break; case QEMU_BLOCKJOB_TYPE_BACKUP: + qemuBlockJobProcessEventConcludedBackup(driver, vm, job, asyncJob, job->newstate); case QEMU_BLOCKJOB_TYPE_NONE: case QEMU_BLOCKJOB_TYPE_INTERNAL: case QEMU_BLOCKJOB_TYPE_LAST: diff --git a/src/qemu/qemu_monitor_json.c b/src/qemu/qemu_monitor_json.c index 51603d2631..e4f1db284d 100644 --- a/src/qemu/qemu_monitor_json.c +++ b/src/qemu/qemu_monitor_json.c @@ -1152,6 +1152,8 @@ qemuMonitorJSONHandleBlockJobImpl(qemuMonitorPtr mon, type = VIR_DOMAIN_BLOCK_JOB_TYPE_COMMIT; else if (STREQ(type_str, "mirror")) type = VIR_DOMAIN_BLOCK_JOB_TYPE_COPY; + else if (STREQ(type_str, "backup")) + type = VIR_DOMAIN_BLOCK_JOB_TYPE_BACKUP; switch ((virConnectDomainEventBlockJobStatus) event) { case VIR_DOMAIN_BLOCK_JOB_COMPLETED: @@ -4858,6 +4860,8 @@ qemuMonitorJSONParseBlockJobInfo(virHashTablePtr blockJobs, info->type = VIR_DOMAIN_BLOCK_JOB_TYPE_COMMIT; else if (STREQ(type, "mirror")) info->type = VIR_DOMAIN_BLOCK_JOB_TYPE_COPY; + else if (STREQ(type, "backup")) + info->type = VIR_DOMAIN_BLOCK_JOB_TYPE_BACKUP; else info->type = VIR_DOMAIN_BLOCK_JOB_TYPE_UNKNOWN; -- 2.21.0

On Fri, Oct 18, 2019 at 18:10:45 +0200, Peter Krempa wrote: [...] For convenience you can fetch it from git fetch https://gitlab.com/pipo.sk/libvirt.git incremental-backup-rfc
participants (5)
-
Daniel Henrique Barboza
-
Daniel P. Berrangé
-
Eric Blake
-
Ján Tomko
-
Peter Krempa