[libvirt] [PATCH v4 12/20] wip: backup: Parse and output backup XML

Eric Blake eblake at redhat.com
Mon Feb 25 21:25:04 UTC 2019


On 2/12/19 12:08 PM, John Ferlan wrote:
> 
> 
> On 2/6/19 2:18 PM, Eric Blake wrote:
>> Accept XML describing a generic block job, and output it again
>> as needed. At the moment, it has some qemu-specific hacks, such
>> as storing internal XML for a node name, that might be cleaner
>> once full-tree node-name support goes in.
>>
>> Still not done: decent tests
>>
>> Signed-off-by: Eric Blake <eblake at redhat.com>
>> ---
>>  src/conf/checkpoint_conf.h |  65 +++++
>>  src/conf/domain_conf.h     |   3 +
>>  src/conf/checkpoint_conf.c | 503 +++++++++++++++++++++++++++++++++++++
>>  src/libvirt_private.syms   |   8 +-
>>  4 files changed, 578 insertions(+), 1 deletion(-)
>>
> 
> I think this should be it's own module src/conf/backup_conf.{c,h}

That one makes sense to me. Even if the public API lives directly in
libvirt-domain.c (instead of my current placement in
libvirt-domain-checkpoint.c), the backend for this XML is sufficient
enough for its own file.  Will split.


>> +
>> +    /* Needed? A way for users to list a disk and explicitly mark it
>> +     * as not participating, and then output shows all disks rather
>> +     * than just active disks */
>> +#if 0
>> +    backup = virXMLPropString(node, "backup");
>> +    if (backup) {
>> +        def->type = virDomainCheckpointTypeFromString(checkpoint);
>> +        if (def->type <= 0) {
>> +            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
>> +                           _("unknown disk checkpoint setting '%s'"),
>> +                           checkpoint);
>> +            goto cleanup;
>> +        }
>> +    }
>> +#endif
> 
> Still need to decide...

Yes, I do.

> 
>> +
>> +    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);
>> +            goto cleanup;
>> +        }
>> +    } else {
>> +        def->store->type = VIR_STORAGE_TYPE_FILE;
>> +    }
>> +
>> +    if ((cur = virXPathNode(push ? "./target" : "./scratch", ctxt)) &&
>> +        virDomainDiskSourceParse(cur, ctxt, def->store, 0, xmlopt) < 0)
>> +        goto cleanup;
>> +
>> +    if (internal) {
> 
> Is this another way of using FLAGS to determine the parse mode?  IOW, is
> this only needed when parsing the status/running XML.
> 

Yes, it is for internal-use XML, and I already know I need to fix things
to use a single 'flags' argument with sane enum values (matching what I
already cleaned up for snapshots).


>> +
>> +    if (flags & VIR_DOMAIN_BACKUP_PARSE_INTERNAL) {
>> +        char *tmp = virXMLPropString(ctxt->node, "id");
> 
> You can use VIR_AUTOFREE(char *) tmp too...
> 
> Ahh... so this is where it's referenced... I recall this from RNG...

Which also raises the question of whether the RNG has to call it out, if
the only thing using it is internal code, or if the user will ever see
it.  We have the interesting problem of outputting some things as
user-visible which are output-only, but then having to validate them in
the RNG on reparse even if we are going to ignore them, so that the user
doesn't have to trim them out.  I'll have to double-check my intent
here, and will leave an appropriate comment (probably along the lines of
"id is valid in output and hence part of the RNG, but ignored on input
except when parsing internal XML for reconstructing job state across
libvirtd restarts").


>> +    if (node) {
>> +        if (def->type != VIR_DOMAIN_BACKUP_TYPE_PULL) {
>> +            virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
>> +                           _("use of <server> requires pull mode backup"));
>> +            goto cleanup;
>> +        }
>> +        if (VIR_ALLOC(def->server) < 0)
>> +            goto cleanup;
>> +        if (virDomainStorageNetworkParseHost(node, def->server) < 0)
>> +            goto cleanup;
>> +        if (def->server->transport == VIR_STORAGE_NET_HOST_TRANS_RDMA) {
>> +            virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
>> +                           _("transport rdma is not supported for <server>"));
> 
> If transport == FOO is ever added and not supported, then you're in
> trouble... Go with
> 
>     if (def->server->transport != VIR_STORAGE_NET_HOST_TRANS_...)
> {TCP|UNIX}
> 
> and use the virStorageHostNetTransportTypeToString to translate what is
> not supported...

Good call.


>> +static int
>> +virDomainBackupDiskDefFormat(virBufferPtr buf,
>> +                             virDomainBackupDiskDefPtr disk,
>> +                             bool push, bool internal)
> 
> one arg per line
> 
> Should @internal make use of flags instead?

Yes, especially since I just fixed that in snapshots.

> 
>> +{
>> +    int type = disk->store->type;
>> +    virBuffer attrBuf = VIR_BUFFER_INITIALIZER;
>> +    virBuffer childBuf = VIR_BUFFER_INITIALIZER;
>> +    int ret = -1;
>> +
>> +    if (!disk->name)
>> +        return 0;
>> +
>> +    virBufferEscapeString(buf, "<disk name='%s'", disk->name);
>> +    /* TODO: per-disk backup=off? */
> 
> ... I assume this is the <backup> noted earlier.

Yes, matches the earlier #if 0 code where I still need to make a decision.

> 
>> +
>> +    virBufferAsprintf(buf, " type='%s'>\n", virStorageTypeToString(type));
>> +    virBufferAdjustIndent(buf, 2);
>> +
>> +    if (disk->store->format > 0)
>> +        virBufferEscapeString(buf, "<driver type='%s'/>\n",
>> +                              virStorageFileFormatTypeToString(disk->store->format));
>> +    /* TODO: should node names be part of storage file xml, rather
>> +     * than a one-off hack for qemu? */
> 
> Do we really want to store them in two places?  I think it's been hard
> enough with just one.
> 
> Hazards of a design where the checkpoint and/or backup are not
> subelements of the disk I suppose.

I've already had quite a bit of a battle making checkpoints work sanely
across qemu restarts (let alone libvirtd restarts); v5 already has some
different code here.  Basically, libvirt has to recompute the node name
any time it connects to a new qemu instance (well, new compared to that
libvirtd process), but caching the node names internally is useful. This
may clean up somewhat if I rebase on top of Peter's blockdev work, but
I'm also trying to make my approaches work without being stuck waiting
for his work to land.  (Ideally, when blockdev DOES land, the API parts
are all stable, and only the internals have to change - so I _really_
need to make sure that no XML locks us in to a particular node name
being stored on disk, as that might be incompatible with blockdev
refactoring down the road).


>> +static int
>> +virDomainBackupDefAssignStore(virDomainBackupDiskDefPtr disk,
>> +                              virStorageSourcePtr src,
>> +                              const char *suffix)
>> +{
>> +    int ret = -1;
>> +
>> +    if (virStorageSourceIsEmpty(src)) {
>> +        if (disk->store) {
>> +            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
>> +                           _("disk '%s' has no media"), disk->name);
>> +            goto cleanup;
>> +        }
>> +    } else if (src->readonly && disk->store) {
>> +        virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
>> +                       _("backup of readonly disk '%s' makes no sense"),
>> +                       disk->name);
>> +        goto cleanup;
>> +    } else if (!disk->store) {
>> +        if (virStorageSourceGetActualType(src) == VIR_STORAGE_TYPE_FILE) {
>> +            if (VIR_ALLOC(disk->store) < 0)
>> +                goto cleanup;
>> +            disk->store->type = VIR_STORAGE_TYPE_FILE;
>> +            if (virAsprintf(&disk->store->path, "%s.%s", src->path,
>> +                            suffix) < 0)
>> +                goto cleanup;
>> +            disk->store->detected = true;
>> +        } else {
>> +            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
>> +                           _("refusing to generate file name for disk '%s'"),
>> +                           disk->name);
>> +            goto cleanup;
>> +        }
>> +    }
> 
> does there need to be an else here ? e.g. can disk->store be set and
> @src not assigned there?  The caller I assume knows to manage @src
> afterwards.

Remember the big #if 0? This is all a hack of using 'disk->store' as a
boolean on whether to visit the disk at the same time as using it as a
pointer to what disk storage to use when it IS being backed up.
Splitting things into a separate flag field may make the code easier to
reason about.

> 
>> +    ret = 0;
>> + cleanup:
>> +    return ret;
> 
> Since there's no cleanup to be done, we could just return directly.
> 
>> +}
>> +
>> +/* Align def->disks to domain.  Sort the list of def->disks,
>> + * generating storage names using suffix as needed.  Convert paths to
>> + * disk targets for uniformity.  Issue an error and return -1 if any
>> + * def->disks[n]->name appears more than once or does not map to
>> + * dom->disks. */
> 
> Similar locking concerns as previously - that is locking domain object.
> 
> This is really familiar code too - perhaps some code sharability is
> possible.

Yes, now all threee of snapshots, checkpoints, and backups have some
form of a AlignDisks function.  I'll see if I can come up with a good
split (separating the sorting part that determines how the user's input
maps to the domain, from the validation part that fills in defaults for
anything the user didn't supply explicitly).

> 
>> +int
>> +virDomainBackupAlignDisks(virDomainBackupDefPtr def, virDomainDefPtr dom,
>> +                          const char *suffix)
>> +{
>> +    int ret = -1;
>> +    virBitmapPtr map = NULL;
>> +    size_t i;
>> +    int ndisks;
>> +    bool alloc_all = false;
> 
>     VIR_AUTOPTR(virBitmap) map = NULL;
> 
> (could be useful other times too, but I just noticed it).
> 
>> +
>> +    if (def->ndisks > dom->ndisks) {
>> +        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
>> +                       _("too many disk backup requests for domain"));
>> +        goto cleanup;
>> +    }
>> +
>> +    /* 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 "
>> +                         "backups"));
>> +        goto cleanup;
>> +    }
>> +
>> +    if (!(map = virBitmapNew(dom->ndisks)))
>> +        goto cleanup;
>> +
>> +    /* Double check requested disks.  */
>> +    for (i = 0; i < def->ndisks; i++) {
>> +        virDomainBackupDiskDefPtr disk = &def->disks[i];
>> +        int idx = virDomainDiskIndexByName(dom, disk->name, false);
>> +
>> +        if (idx < 0) {
>> +            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
>> +                           _("no disk named '%s'"), disk->name);
>> +            goto cleanup;
>> +        }
>> +
>> +        if (virBitmapIsBitSet(map, idx)) {
>> +            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
>> +                           _("disk '%s' specified twice"),
>> +                           disk->name);
>> +            goto cleanup;
>> +        }
>> +        ignore_value(virBitmapSetBit(map, idx));
>> +        disk->idx = idx;
>> +
>> +        if (STRNEQ(disk->name, dom->disks[idx]->dst)) {
> 
> Trying to picture how this happens... should this use STRNEQ_NULLABLE?

No, both disk->name and dom->disks[idx]->dst are non-NULL as part of
their XML parse.

> 
>> +            VIR_FREE(disk->name);
>> +            if (VIR_STRDUP(disk->name, dom->disks[idx]->dst) < 0)
>> +                goto cleanup;
>> +        }
>> +        if (disk->store && !disk->store->path) {
>> +            virStorageSourceClear(disk->store);
> 
> But no VIR_FREE(disk->source) how does that play with the following?
> 
>> +            disk->store = NULL;
>> +        }
>> +        if (virDomainBackupDefAssignStore(disk, dom->disks[i]->src, suffix) < 0)
>> +            goto cleanup;
>> +    }
>> +
>> +    /* Provide fillers for all remaining disks, for easier iteration.  */
> 
> But is it "hooked up" with hotplug/hotunplug?
> 
> Beginning to think/wonder why backup/checkpoint isn't a child of storage
> source.
> 

Storage source visits only one disk, while snapshot/checkpoint visit all
of the domain's disks at once. But you are also right that having
storage sources be smarter about things may help.

> 
>> +    if (!def->ndisks)
>> +        alloc_all = true;
>> +    ndisks = def->ndisks;
>> +    if (VIR_EXPAND_N(def->disks, def->ndisks,
>> +                     dom->ndisks - def->ndisks) < 0)
>> +        goto cleanup;
>> +
>> +    for (i = 0; i < dom->ndisks; i++) {
>> +        virDomainBackupDiskDefPtr disk;
>> +
>> +        if (virBitmapIsBitSet(map, i))
>> +            continue;
>> +        disk = &def->disks[ndisks++];
>> +        if (VIR_STRDUP(disk->name, dom->disks[i]->dst) < 0)
>> +            goto cleanup;
>> +        disk->idx = i;
>> +        if (alloc_all &&
>> +            virDomainBackupDefAssignStore(disk, dom->disks[i]->src, suffix) < 0)
>> +            goto cleanup;
>> +    }
>> +
>> +    qsort(&def->disks[0], def->ndisks, sizeof(def->disks[0]),
>> +          virDomainBackupCompareDiskIndex);
>> +
>> +    ret = 0;
>> +
>> + cleanup:
>> +    virBitmapFree(map);
> 
> Using autofree stuff means the cleanup is unnecessary and we return
> directly.
> 
> 
> John
> 
>> +    return ret;
>> +}
>> diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
>> index fbe7ba2d40..b9b7684494 100644
>> --- a/src/libvirt_private.syms
>> +++ b/src/libvirt_private.syms
>> @@ -69,6 +69,13 @@ virCapabilitiesSetNetPrefix;
>>
>>
>>  # conf/checkpoint_conf.h
>> +virDomainBackupAlignDisks;
>> +virDomainBackupDefFormat;
>> +virDomainBackupDefFree;
>> +virDomainBackupDefParseNode;
>> +virDomainBackupDefParseString;
>> +virDomainBackupTypeFromString;
>> +virDomainBackupTypeToString;
>>  virDomainCheckpointAlignDisks;
>>  virDomainCheckpointAssignDef;
>>  virDomainCheckpointDefFormat;
>> @@ -88,7 +95,6 @@ virDomainCheckpointTypeToString;
>>  virDomainCheckpointUpdateRelations;
>>  virDomainListAllCheckpoints;
>>
>> -
>>  # conf/cpu_conf.h
>>  virCPUCacheModeTypeFromString;
>>  virCPUCacheModeTypeToString;
>>
> 

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3226
Virtualization:  qemu.org | libvirt.org




More information about the libvir-list mailing list