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@xxxxxxxxxx> --- src/conf/checkpoint_conf.h | 66 +++++ src/conf/domain_conf.h | 3 + src/conf/checkpoint_conf.c | 503 +++++++++++++++++++++++++++++++++++++ src/libvirt_private.syms | 8 +- 4 files changed, 579 insertions(+), 1 deletion(-) diff --git a/src/conf/checkpoint_conf.h b/src/conf/checkpoint_conf.h index 45ed8b0d29..2d18b7d0fb 100644 --- a/src/conf/checkpoint_conf.h +++ b/src/conf/checkpoint_conf.h @@ -168,4 +168,70 @@ int virDomainCheckpointRedefinePrep(virDomainPtr domain, VIR_ENUM_DECL(virDomainCheckpoint); +/* 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_DEFAULT = 0, /* Initial */ + VIR_DOMAIN_BACKUP_DISK_STATE_CREATED, /* File created */ + VIR_DOMAIN_BACKUP_DISK_STATE_LABEL, /* Security labels applied */ + VIR_DOMAIN_BACKUP_DISK_STATE_READY, /* Handed to guest */ + VIR_DOMAIN_BACKUP_DISK_STATE_BITMAP, /* Associated temp bitmap created */ + VIR_DOMAIN_BACKUP_DISK_STATE_EXPORT, /* NBD export created */ + VIR_DOMAIN_BACKUP_DISK_STATE_COMPLETE, /* Push job finished */ +} virDomainBackupDiskState; + +/* Stores disk-backup information */ +typedef struct _virDomainBackupDiskDef virDomainBackupDiskDef; +typedef virDomainBackupDiskDef *virDomainBackupDiskDefPtr; +struct _virDomainBackupDiskDef { + char *name; /* name matching the <target dev='...' of the domain */ + int idx; /* index within checkpoint->dom->disks that matches name */ + + /* details of target for push-mode, or of the scratch file for pull-mode */ + virStorageSourcePtr store; + int state; /* virDomainBackupDiskState, not stored in XML */ +}; + +/* 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; +}; + +VIR_ENUM_DECL(virDomainBackup); + +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); +int virDomainBackupDefFormat(virBufferPtr buf, + virDomainBackupDefPtr def, + bool internal); +int virDomainBackupAlignDisks(virDomainBackupDefPtr backup, + virDomainDefPtr dom, const char *suffix); + #endif /* LIBVIRT_CHECKPOINT_CONF_H */ diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index c3d5f53bdd..ea98dd31d9 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -125,6 +125,9 @@ typedef virDomainCheckpointObj *virDomainCheckpointObjPtr; typedef struct _virDomainCheckpointObjList virDomainCheckpointObjList; typedef virDomainCheckpointObjList *virDomainCheckpointObjListPtr; +typedef struct _virDomainBackupDef virDomainBackupDef; +typedef virDomainBackupDef *virDomainBackupDefPtr; + typedef struct _virDomainSnapshotObj virDomainSnapshotObj; typedef virDomainSnapshotObj *virDomainSnapshotObjPtr; diff --git a/src/conf/checkpoint_conf.c b/src/conf/checkpoint_conf.c index e5e4c5e306..61aeb7f0a1 100644 --- a/src/conf/checkpoint_conf.c +++ b/src/conf/checkpoint_conf.c @@ -1238,3 +1238,506 @@ virDomainCheckpointRedefinePrep(virDomainPtr domain, cleanup: return ret; } + +/* Backup Def functions */ + +VIR_ENUM_IMPL(virDomainBackup, VIR_DOMAIN_BACKUP_TYPE_LAST, + "default", "push", "pull"); + +static void +virDomainBackupDiskDefClear(virDomainBackupDiskDefPtr disk) +{ + VIR_FREE(disk->name); + virStorageSourceClear(disk->store); + disk->store = NULL; +} + +void +virDomainBackupDefFree(virDomainBackupDefPtr def) +{ + size_t i; + + if (!def) + return; + + VIR_FREE(def->incremental); + VIR_FREE(def->server); // FIXME which struct + for (i = 0; i < def->ndisks; i++) + virDomainBackupDiskDefClear(&def->disks[i]); + VIR_FREE(def->disks); + VIR_FREE(def); +} + +static int +virDomainBackupDiskDefParseXML(xmlNodePtr node, + xmlXPathContextPtr ctxt, + virDomainBackupDiskDefPtr def, + bool push, bool internal, + virDomainXMLOptionPtr xmlopt) +{ + int ret = -1; + // char *backup = NULL; /* backup="yes|no"? */ + char *type = NULL; + char *driver = NULL; + xmlNodePtr cur; + xmlNodePtr saved = ctxt->node; + + ctxt->node = node; + + if (VIR_ALLOC(def->store) < 0) + goto cleanup; + + def->name = virXMLPropString(node, "name"); + if (!def->name) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing name from disk backup element")); + goto cleanup; + } + + /* 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 + + 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) { + int detected; + if (virXPathInt("string(./node/@detected)", ctxt, &detected) < 0) + goto cleanup; + def->store->detected = detected; + def->store->nodeformat = virXPathString("string(./node)", ctxt); + } + + 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); + goto cleanup; + } else if (!push && def->store->format != VIR_STORAGE_FILE_QCOW2) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("pull mode requires qcow2 driver, not '%s'"), + driver); + goto cleanup; + } + } + + /* validate that the passed path is absolute */ + if (virStorageSourceIsRelative(def->store)) { + virReportError(VIR_ERR_XML_ERROR, + _("disk backup image path '%s' must be absolute"), + def->store->path); + goto cleanup; + } + + ret = 0; + cleanup: + ctxt->node = saved; + + VIR_FREE(driver); +// VIR_FREE(backup); + VIR_FREE(type); + if (ret < 0) + virDomainBackupDiskDefClear(def); + return ret; +} + +static virDomainBackupDefPtr +virDomainBackupDefParse(xmlXPathContextPtr ctxt, + virDomainXMLOptionPtr xmlopt, + unsigned int flags) +{ + virDomainBackupDefPtr def = NULL; + virDomainBackupDefPtr ret = NULL; + xmlNodePtr *nodes = NULL; + xmlNodePtr node = NULL; + char *mode = NULL; + bool push; + size_t i; + int n; + + if (VIR_ALLOC(def) < 0) + goto cleanup; + + mode = virXMLPropString(ctxt->node, "mode"); + if (mode) { + def->type = virDomainBackupTypeFromString(mode); + if (def->type <= 0) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("unknown backup mode '%s'"), mode); + goto cleanup; + } + } else { + def->type = VIR_DOMAIN_BACKUP_TYPE_PUSH; + } + push = def->type == VIR_DOMAIN_BACKUP_TYPE_PUSH; + + if (flags & VIR_DOMAIN_BACKUP_PARSE_INTERNAL) { + 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); + VIR_FREE(tmp); + goto cleanup; + } + VIR_FREE(tmp); + } + + def->incremental = virXPathString("string(./incremental)", ctxt); + + node = virXPathNode("./server", ctxt); + 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>")); + goto cleanup; + } + } + + if ((n = virXPathNodeSet("./disks/*", ctxt, &nodes)) < 0) + goto cleanup; + if (n && VIR_ALLOC_N(def->disks, n) < 0) + goto cleanup; + def->ndisks = n; + for (i = 0; i < def->ndisks; i++) { + if (virDomainBackupDiskDefParseXML(nodes[i], ctxt, + &def->disks[i], push, + flags & VIR_DOMAIN_BACKUP_PARSE_INTERNAL, + xmlopt) < 0) + goto cleanup; + } + VIR_FREE(nodes); + + VIR_STEAL_PTR(ret, def); + + cleanup: + VIR_FREE(mode); + VIR_FREE(nodes); + virDomainBackupDefFree(def); + + return ret; +} + +virDomainBackupDefPtr +virDomainBackupDefParseString(const char *xmlStr, + virDomainXMLOptionPtr xmlopt, + unsigned int flags) +{ + virDomainBackupDefPtr ret = NULL; + xmlDocPtr xml; + int keepBlanksDefault = xmlKeepBlanksDefault(0); + + if ((xml = virXMLParse(NULL, xmlStr, _("(domain_backup)")))) { + xmlKeepBlanksDefault(keepBlanksDefault); + ret = virDomainBackupDefParseNode(xml, xmlDocGetRootElement(xml), + xmlopt, flags); + xmlFreeDoc(xml); + } + xmlKeepBlanksDefault(keepBlanksDefault); + + return ret; +} + +virDomainBackupDefPtr +virDomainBackupDefParseNode(xmlDocPtr xml, + xmlNodePtr root, + virDomainXMLOptionPtr xmlopt, + unsigned int flags) +{ + xmlXPathContextPtr ctxt = NULL; + virDomainBackupDefPtr def = NULL; + + if (!virXMLNodeNameEqual(root, "domainbackup")) { + virReportError(VIR_ERR_XML_ERROR, "%s", _("domainbackup")); + goto cleanup; + } + + ctxt = xmlXPathNewContext(xml); + if (ctxt == NULL) { + virReportOOMError(); + goto cleanup; + } + + ctxt->node = root; + def = virDomainBackupDefParse(ctxt, xmlopt, flags); + cleanup: + xmlXPathFreeContext(ctxt); + return def; +} + +static int +virDomainBackupDiskDefFormat(virBufferPtr buf, + virDomainBackupDiskDefPtr disk, + bool push, bool internal) +{ + 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? */ + + 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? */ + if (internal) { + virBufferEscapeString(buf, "<node detected='%s'", + disk->store->detected ? "1" : "0"); + virBufferEscapeString(buf, ">%s</node>\n", disk->store->nodeformat); + } + + virBufferSetChildIndent(&childBuf, buf); + if (virDomainStorageSourceFormat(&attrBuf, &childBuf, disk->store, 0, + false) < 0) + goto cleanup; + if (virXMLFormatElement(buf, push ? "target" : "scratch", + &attrBuf, &childBuf) < 0) + goto cleanup; + + virBufferAdjustIndent(buf, -2); + virBufferAddLit(buf, "</disk>\n"); + + ret = 0; + + cleanup: + virBufferFreeAndReset(&attrBuf); + virBufferFreeAndReset(&childBuf); + return ret; +} + +int +virDomainBackupDefFormat(virBufferPtr buf, virDomainBackupDefPtr def, + bool internal) +{ + size_t i; + + virBufferAsprintf(buf, "<domainbackup mode='%s'", + virDomainBackupTypeToString(def->type)); + if (def->id) + virBufferAsprintf(buf, " id='%d'", def->id); + virBufferAddLit(buf, ">\n"); + virBufferAdjustIndent(buf, 2); + + virBufferEscapeString(buf, "<incremental>%s</incremental>\n", + def->incremental); + if (def->server) { + virBufferAsprintf(buf, "<server transport='%s'", + virStorageNetHostTransportTypeToString(def->server->transport)); + virBufferEscapeString(buf, " name='%s'", def->server->name); + if (def->server->port) + virBufferAsprintf(buf, " port='%u'", def->server->port); + virBufferEscapeString(buf, " socket='%s'", def->server->socket); + virBufferAddLit(buf, "/>\n"); + } + + if (def->ndisks) { + virBufferAddLit(buf, "<disks>\n"); + virBufferAdjustIndent(buf, 2); + for (i = 0; i < def->ndisks; i++) { + if (!def->disks[i].store) + continue; + if (virDomainBackupDiskDefFormat(buf, &def->disks[i], + def->type == VIR_DOMAIN_BACKUP_TYPE_PUSH, + internal) < 0) + return -1; + } + virBufferAdjustIndent(buf, -2); + virBufferAddLit(buf, "</disks>\n"); + } + + virBufferAdjustIndent(buf, -2); + virBufferAddLit(buf, "</domainbackup>\n"); + + return virBufferCheckError(buf); +} + + +static int +virDomainBackupCompareDiskIndex(const void *a, const void *b) +{ + const virDomainBackupDiskDef *diska = a; + const virDomainBackupDiskDef *diskb = b; + + /* Integer overflow shouldn't be a problem here. */ + return diska->idx - diskb->idx; +} + +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; + } + } + ret = 0; + cleanup: + return ret; +} + +/* 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. */ +int +virDomainBackupAlignDisks(virDomainBackupDefPtr def, virDomainDefPtr dom, + const char *suffix) +{ + int ret = -1; + virBitmapPtr map = NULL; + size_t i; + int ndisks; + bool alloc_all = false; + + 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)) { + 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); + disk->store = NULL; + } + if (virDomainBackupDefAssignStore(disk, dom->disks[i]->src, suffix) < 0) + goto cleanup; + } + + /* Provide fillers for all remaining disks, for easier iteration. */ + 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); + return ret; +} diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 84e5d3dea2..053fa8bc8b 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -70,6 +70,13 @@ virCapabilitiesSetNetPrefix; # conf/checkpoint_conf.h +virDomainBackupAlignDisks; +virDomainBackupDefFormat; +virDomainBackupDefFree; +virDomainBackupDefParseNode; +virDomainBackupDefParseString; +virDomainBackupTypeFromString; +virDomainBackupTypeToString; virDomainCheckpointAlignDisks; virDomainCheckpointAssignDef; virDomainCheckpointDefFormat; @@ -92,7 +99,6 @@ virDomainCheckpointTypeToString; virDomainCheckpointUpdateRelations; virDomainListAllCheckpoints; - # conf/cpu_conf.h virCPUCacheModeTypeFromString; virCPUCacheModeTypeToString; -- 2.20.1 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list