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(a)redhat.com>
---
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