Work in progress - the checkpoint code is not quite passing tests (part of that is figuring out the minimal XML that is still valid as a <domain> element, or just use --no-domain flag). Signed-off-by: Eric Blake <eblake@xxxxxxxxxx> --- src/conf/checkpoint_conf.h | 150 ++++ src/conf/domain_conf.h | 11 +- po/POTFILES | 1 + src/conf/Makefile.inc.am | 2 + src/conf/checkpoint_conf.c | 1030 +++++++++++++++++++++++++++ src/conf/domain_conf.c | 7 +- src/libvirt_private.syms | 21 + tests/Makefile.am | 9 +- tests/domaincheckpointxml2xmltest.c | 231 ++++++ 9 files changed, 1458 insertions(+), 4 deletions(-) create mode 100644 src/conf/checkpoint_conf.h create mode 100644 src/conf/checkpoint_conf.c create mode 100644 tests/domaincheckpointxml2xmltest.c diff --git a/src/conf/checkpoint_conf.h b/src/conf/checkpoint_conf.h new file mode 100644 index 0000000000..994a8bd083 --- /dev/null +++ b/src/conf/checkpoint_conf.h @@ -0,0 +1,150 @@ +/* + * checkpoint_conf.h: domain checkpoint XML processing + * + * 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/>. + */ + +#ifndef LIBVIRT_CHECKPOINT_CONF_H +# define LIBVIRT_CHECKPOINT_CONF_H + +# include "internal.h" +# include "domain_conf.h" + +/* Items related to checkpoint state */ + +typedef enum { + VIR_DOMAIN_CHECKPOINT_TYPE_DEFAULT = 0, + VIR_DOMAIN_CHECKPOINT_TYPE_NONE, + VIR_DOMAIN_CHECKPOINT_TYPE_BITMAP, + + VIR_DOMAIN_CHECKPOINT_TYPE_LAST +} virDomainCheckpointType; + +/* Stores disk-checkpoint information */ +typedef struct _virDomainCheckpointDiskDef virDomainCheckpointDiskDef; +typedef virDomainCheckpointDiskDef *virDomainCheckpointDiskDefPtr; +struct _virDomainCheckpointDiskDef { + char *name; /* name matching the <target dev='...' of the domain */ + int idx; /* index within checkpoint->dom->disks that matches name */ + int type; /* virDomainCheckpointType */ + char *bitmap; /* bitmap name, if type is bitmap */ + unsigned long long size; /* current checkpoint size in bytes */ +}; + +/* Stores the complete checkpoint metadata */ +typedef struct _virDomainCheckpointDef virDomainCheckpointDef; +typedef virDomainCheckpointDef *virDomainCheckpointDefPtr; +struct _virDomainCheckpointDef { + /* Public XML. */ + char *name; + char *description; + char *parent; + long long creationTime; /* in seconds */ + + size_t ndisks; /* should not exceed dom->ndisks */ + virDomainCheckpointDiskDef *disks; + + virDomainDefPtr dom; + + /* Internal use. */ + bool current; /* At most one checkpoint in the list should have this set */ +}; + +struct _virDomainCheckpointObj { + virDomainCheckpointDefPtr def; /* non-NULL except for metaroot */ + + virDomainCheckpointObjPtr parent; /* non-NULL except for metaroot, before + virDomainCheckpointUpdateRelations, or + after virDomainCheckpointDropParent */ + virDomainCheckpointObjPtr sibling; /* NULL if last child of parent */ + size_t nchildren; + virDomainCheckpointObjPtr first_child; /* NULL if no children */ +}; + +virDomainCheckpointObjListPtr virDomainCheckpointObjListNew(void); +void virDomainCheckpointObjListFree(virDomainCheckpointObjListPtr checkpoints); + +typedef enum { + VIR_DOMAIN_CHECKPOINT_PARSE_REDEFINE = 1 << 0, + VIR_DOMAIN_CHECKPOINT_PARSE_DISKS = 1 << 1, + VIR_DOMAIN_CHECKPOINT_PARSE_INTERNAL = 1 << 2, +} virDomainCheckpointParseFlags; + +virDomainCheckpointDefPtr virDomainCheckpointDefParseString(const char *xmlStr, + virCapsPtr caps, + virDomainXMLOptionPtr xmlopt, + unsigned int flags); +virDomainCheckpointDefPtr virDomainCheckpointDefParseNode(xmlDocPtr xml, + xmlNodePtr root, + virCapsPtr caps, + virDomainXMLOptionPtr xmlopt, + unsigned int flags); +void virDomainCheckpointDefFree(virDomainCheckpointDefPtr def); +char *virDomainCheckpointDefFormat(virDomainCheckpointDefPtr def, + virCapsPtr caps, + virDomainXMLOptionPtr xmlopt, + unsigned int flags, + bool internal); +int virDomainCheckpointAlignDisks(virDomainCheckpointDefPtr checkpoint); +virDomainCheckpointObjPtr virDomainCheckpointAssignDef(virDomainCheckpointObjListPtr checkpoints, + virDomainCheckpointDefPtr def); + +virDomainCheckpointObjPtr virDomainCheckpointFindByName(virDomainCheckpointObjListPtr checkpoints, + const char *name); +void virDomainCheckpointObjListRemove(virDomainCheckpointObjListPtr checkpoints, + virDomainCheckpointObjPtr checkpoint); +int virDomainCheckpointForEach(virDomainCheckpointObjListPtr checkpoints, + virHashIterator iter, + void *data); +int virDomainCheckpointForEachChild(virDomainCheckpointObjPtr checkpoint, + virHashIterator iter, + void *data); +int virDomainCheckpointForEachDescendant(virDomainCheckpointObjPtr checkpoint, + virHashIterator iter, + void *data); +int virDomainCheckpointUpdateRelations(virDomainCheckpointObjListPtr checkpoints); +void virDomainCheckpointDropParent(virDomainCheckpointObjPtr checkpoint); + +# define VIR_DOMAIN_CHECKPOINT_FILTERS_METADATA \ + (VIR_DOMAIN_CHECKPOINT_LIST_METADATA | \ + VIR_DOMAIN_CHECKPOINT_LIST_NO_METADATA) + +# define VIR_DOMAIN_CHECKPOINT_FILTERS_LEAVES \ + (VIR_DOMAIN_CHECKPOINT_LIST_LEAVES | \ + VIR_DOMAIN_CHECKPOINT_LIST_NO_LEAVES) + +# define VIR_DOMAIN_CHECKPOINT_FILTERS_ALL \ + (VIR_DOMAIN_CHECKPOINT_FILTERS_METADATA | \ + VIR_DOMAIN_CHECKPOINT_FILTERS_LEAVES) + +int virDomainListAllCheckpoints(virDomainCheckpointObjListPtr checkpoints, + virDomainCheckpointObjPtr from, + virDomainPtr dom, + virDomainCheckpointPtr **objs, + unsigned int flags); + +int virDomainCheckpointRedefinePrep(virDomainPtr domain, + virDomainObjPtr vm, + virDomainCheckpointDefPtr *def, + virDomainCheckpointObjPtr *checkpoint, + virDomainXMLOptionPtr xmlopt, + bool *update_current); + +VIR_ENUM_DECL(virDomainCheckpoint); + +#endif /* LIBVIRT_CHECKPOINT_CONF_H */ diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index 5db4396fd5..d31c45427e 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -1,7 +1,7 @@ /* * domain_conf.h: domain XML processing * - * Copyright (C) 2006-2016 Red Hat, Inc. + * Copyright (C) 2006-2018 Red Hat, Inc. * Copyright (C) 2006-2008 Daniel P. Berrange * Copyright (c) 2015 SUSE LINUX Products GmbH, Nuernberg, Germany. * @@ -119,6 +119,12 @@ typedef virDomainMemballoonDef *virDomainMemballoonDefPtr; typedef struct _virDomainNVRAMDef virDomainNVRAMDef; typedef virDomainNVRAMDef *virDomainNVRAMDefPtr; +typedef struct _virDomainCheckpointObj virDomainCheckpointObj; +typedef virDomainCheckpointObj *virDomainCheckpointObjPtr; + +typedef struct _virDomainCheckpointObjList virDomainCheckpointObjList; +typedef virDomainCheckpointObjList *virDomainCheckpointObjListPtr; + typedef struct _virDomainSnapshotObj virDomainSnapshotObj; typedef virDomainSnapshotObj *virDomainSnapshotObjPtr; @@ -2631,6 +2637,9 @@ struct _virDomainObj { bool hasManagedSave; + virDomainCheckpointObjListPtr checkpoints; + virDomainCheckpointObjPtr current_checkpoint; + void *privateData; void (*privateDataFreeFunc)(void *); diff --git a/po/POTFILES b/po/POTFILES index 88af551664..57c55fb35f 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -15,6 +15,7 @@ src/bhyve/bhyve_monitor.c src/bhyve/bhyve_parse_command.c src/bhyve/bhyve_process.c src/conf/capabilities.c +src/conf/checkpoint_conf.c src/conf/cpu_conf.c src/conf/device_conf.c src/conf/domain_addr.c diff --git a/src/conf/Makefile.inc.am b/src/conf/Makefile.inc.am index 219ff350d7..c425363bde 100644 --- a/src/conf/Makefile.inc.am +++ b/src/conf/Makefile.inc.am @@ -10,6 +10,8 @@ NETDEV_CONF_SOURCES = \ DOMAIN_CONF_SOURCES = \ conf/capabilities.c \ conf/capabilities.h \ + conf/checkpoint_conf.c \ + conf/checkpoint_conf.h \ conf/domain_addr.c \ conf/domain_addr.h \ conf/domain_capabilities.c \ diff --git a/src/conf/checkpoint_conf.c b/src/conf/checkpoint_conf.c new file mode 100644 index 0000000000..c0840a96b2 --- /dev/null +++ b/src/conf/checkpoint_conf.c @@ -0,0 +1,1030 @@ +/* + * checkpoint_conf.c: domain checkpoint XML processing + * + * 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/>. + */ + +#include <config.h> + +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <unistd.h> + +#include "internal.h" +#include "virbitmap.h" +#include "virbuffer.h" +#include "count-one-bits.h" +#include "datatypes.h" +#include "domain_conf.h" +#include "virlog.h" +#include "viralloc.h" +#include "netdev_bandwidth_conf.h" +#include "netdev_vport_profile_conf.h" +#include "nwfilter_conf.h" +#include "secret_conf.h" +#include "checkpoint_conf.h" +#include "virstoragefile.h" +#include "viruuid.h" +#include "virfile.h" +#include "virerror.h" +#include "virxml.h" +#include "virstring.h" + +#define VIR_FROM_THIS VIR_FROM_DOMAIN_CHECKPOINT + +VIR_LOG_INIT("conf.checkpoint_conf"); + +VIR_ENUM_IMPL(virDomainCheckpoint, VIR_DOMAIN_CHECKPOINT_TYPE_LAST, + "default", "no", "bitmap"); + +struct _virDomainCheckpointObjList { + /* name string -> virDomainCheckpointObj mapping + * for O(1), lockless lookup-by-name */ + virHashTable *objs; + + virDomainCheckpointObj metaroot; /* Special parent of all root checkpoints */ +}; + +/* Checkpoint Def functions */ +static void +virDomainCheckpointDiskDefClear(virDomainCheckpointDiskDefPtr disk) +{ + VIR_FREE(disk->name); + VIR_FREE(disk->bitmap); +} + +void virDomainCheckpointDefFree(virDomainCheckpointDefPtr def) +{ + size_t i; + + if (!def) + return; + + VIR_FREE(def->name); + VIR_FREE(def->description); + VIR_FREE(def->parent); + for (i = 0; i < def->ndisks; i++) + virDomainCheckpointDiskDefClear(&def->disks[i]); + VIR_FREE(def->disks); + virDomainDefFree(def->dom); + VIR_FREE(def); +} + +static int +virDomainCheckpointDiskDefParseXML(xmlNodePtr node, + xmlXPathContextPtr ctxt, + virDomainCheckpointDiskDefPtr def) +{ + int ret = -1; + char *checkpoint = NULL; + char *bitmap = NULL; + xmlNodePtr saved = ctxt->node; + + ctxt->node = node; + + def->name = virXMLPropString(node, "name"); + if (!def->name) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing name from disk checkpoint element")); + goto cleanup; + } + + checkpoint = virXMLPropString(node, "checkpoint"); + if (checkpoint) { + def->type = virDomainCheckpointTypeFromString(checkpoint); + if (def->type <= 0) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("unknown disk checkpoint setting '%s'"), + checkpoint); + goto cleanup; + } + } else { + def->type = VIR_DOMAIN_CHECKPOINT_TYPE_BITMAP; + } + + bitmap = virXMLPropString(node, "bitmap"); + if (bitmap) { + if (def->type != VIR_DOMAIN_CHECKPOINT_TYPE_BITMAP) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("disk checkpoint bitmap '%s' requires " + "type='bitmap'"), + bitmap); + goto cleanup; + } + VIR_STEAL_PTR(def->bitmap, bitmap); + } + + ret = 0; + cleanup: + ctxt->node = saved; + + VIR_FREE(checkpoint); + VIR_FREE(bitmap); + if (ret < 0) + virDomainCheckpointDiskDefClear(def); + return ret; +} + +/* flags is bitwise-or of virDomainCheckpointParseFlags. + * If flags does not include VIR_DOMAIN_CHECKPOINT_PARSE_REDEFINE, then + * caps are ignored. + */ +static virDomainCheckpointDefPtr +virDomainCheckpointDefParse(xmlXPathContextPtr ctxt, + virCapsPtr caps, + virDomainXMLOptionPtr xmlopt, + unsigned int flags) +{ + virDomainCheckpointDefPtr def = NULL; + virDomainCheckpointDefPtr ret = NULL; + xmlNodePtr *nodes = NULL; + size_t i; + int n; + char *creation = NULL; + struct timeval tv; + int active; + char *tmp; + + if (VIR_ALLOC(def) < 0) + goto cleanup; + + gettimeofday(&tv, NULL); + + def->name = virXPathString("string(./name)", ctxt); + if (def->name == NULL) { + if (flags & VIR_DOMAIN_CHECKPOINT_PARSE_REDEFINE) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("a redefined checkpoint must have a name")); + goto cleanup; + } + if (virAsprintf(&def->name, "%lld", (long long)tv.tv_sec) < 0) + goto cleanup; + } + + def->description = virXPathString("string(./description)", ctxt); + + if (flags & VIR_DOMAIN_CHECKPOINT_PARSE_REDEFINE) { + if (virXPathLongLong("string(./creationTime)", ctxt, + &def->creationTime) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing creationTime from existing checkpoint")); + goto cleanup; + } + + def->parent = virXPathString("string(./parent/name)", ctxt); + + if ((tmp = virXPathString("string(./domain/@type)", ctxt))) { + int domainflags = VIR_DOMAIN_DEF_PARSE_INACTIVE | + VIR_DOMAIN_DEF_PARSE_SKIP_VALIDATE; + xmlNodePtr domainNode = virXPathNode("./domain", ctxt); + + VIR_FREE(tmp); + if (!domainNode) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing domain in checkpoint")); + goto cleanup; + } + def->dom = virDomainDefParseNode(ctxt->node->doc, domainNode, + caps, xmlopt, NULL, domainflags); + if (!def->dom) + goto cleanup; + } else { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing domain in checkpoint redefine")); + goto cleanup; + } + } else { + def->creationTime = tv.tv_sec; + } + + if ((n = virXPathNodeSet("./disks/*", ctxt, &nodes)) < 0) + goto cleanup; + if (flags & VIR_DOMAIN_CHECKPOINT_PARSE_DISKS) { + if (n && VIR_ALLOC_N(def->disks, n) < 0) + goto cleanup; + def->ndisks = n; + for (i = 0; i < def->ndisks; i++) { + if (virDomainCheckpointDiskDefParseXML(nodes[i], ctxt, + &def->disks[i]) < 0) + goto cleanup; + } + VIR_FREE(nodes); + } else if (n) { + virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", + _("unable to handle disk requests in checkpoint")); + goto cleanup; + } + + if (flags & VIR_DOMAIN_CHECKPOINT_PARSE_INTERNAL) { + if (virXPathInt("string(./active)", ctxt, &active) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Could not find 'active' element")); + goto cleanup; + } + def->current = active != 0; + } + + VIR_STEAL_PTR(ret, def); + + cleanup: + VIR_FREE(creation); + VIR_FREE(nodes); + virDomainCheckpointDefFree(def); + + return ret; +} + +virDomainCheckpointDefPtr +virDomainCheckpointDefParseNode(xmlDocPtr xml, + xmlNodePtr root, + virCapsPtr caps, + virDomainXMLOptionPtr xmlopt, + unsigned int flags) +{ + xmlXPathContextPtr ctxt = NULL; + virDomainCheckpointDefPtr def = NULL; + + if (!virXMLNodeNameEqual(root, "domaincheckpoint")) { + virReportError(VIR_ERR_XML_ERROR, "%s", _("domaincheckpoint")); + goto cleanup; + } + + ctxt = xmlXPathNewContext(xml); + if (ctxt == NULL) { + virReportOOMError(); + goto cleanup; + } + + ctxt->node = root; + def = virDomainCheckpointDefParse(ctxt, caps, xmlopt, flags); + cleanup: + xmlXPathFreeContext(ctxt); + return def; +} + +virDomainCheckpointDefPtr +virDomainCheckpointDefParseString(const char *xmlStr, + virCapsPtr caps, + virDomainXMLOptionPtr xmlopt, + unsigned int flags) +{ + virDomainCheckpointDefPtr ret = NULL; + xmlDocPtr xml; + int keepBlanksDefault = xmlKeepBlanksDefault(0); + + if ((xml = virXMLParse(NULL, xmlStr, _("(domain_checkpoint)")))) { + xmlKeepBlanksDefault(keepBlanksDefault); + ret = virDomainCheckpointDefParseNode(xml, xmlDocGetRootElement(xml), + caps, xmlopt, flags); + xmlFreeDoc(xml); + } + xmlKeepBlanksDefault(keepBlanksDefault); + + return ret; +} + +/** + * virDomainCheckpointDefAssignBitmapNames: + * @def: checkpoint def object + * + * Generate default bitmap names for checkpoint targets. Returns 0 on + * success, -1 on error. + */ +static int +virDomainCheckpointDefAssignBitmapNames(virDomainCheckpointDefPtr def) +{ + size_t i; + + for (i = 0; i < def->ndisks; i++) { + virDomainCheckpointDiskDefPtr disk = &def->disks[i]; + + if (disk->type != VIR_DOMAIN_CHECKPOINT_TYPE_BITMAP || + disk->bitmap) + continue; + + if (VIR_STRDUP(disk->bitmap, def->name) < 0) + return -1; + } + + return 0; +} + + +static int +virDomainCheckpointCompareDiskIndex(const void *a, const void *b) +{ + const virDomainCheckpointDiskDef *diska = a; + const virDomainCheckpointDiskDef *diskb = b; + + /* Integer overflow shouldn't be a problem here. */ + return diska->idx - diskb->idx; +} + +/* Align def->disks to def->domain. Sort the list of def->disks, + * filling in any missing disks with appropriate default. 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 +virDomainCheckpointAlignDisks(virDomainCheckpointDefPtr def) +{ + int ret = -1; + virBitmapPtr map = NULL; + size_t i; + int ndisks; + int checkpoint_default = VIR_DOMAIN_CHECKPOINT_TYPE_NONE; + + if (!def->dom) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing domain in checkpoint")); + goto cleanup; + } + + if (def->ndisks > def->dom->ndisks) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("too many disk checkpoint requests for domain")); + goto cleanup; + } + + /* Unlikely to have a guest without disks but technically possible. */ + if (!def->dom->ndisks) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("domain must have at least one disk to perform " + "checkpoints")); + goto cleanup; + } + + /* If <disks> omitted, do bitmap on all disks; otherwise, do nothing + * for omitted disks */ + if (!def->ndisks) + checkpoint_default = VIR_DOMAIN_CHECKPOINT_TYPE_BITMAP; + + if (!(map = virBitmapNew(def->dom->ndisks))) + goto cleanup; + + /* Double check requested disks. */ + for (i = 0; i < def->ndisks; i++) { + virDomainCheckpointDiskDefPtr disk = &def->disks[i]; + int idx = virDomainDiskIndexByName(def->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, def->dom->disks[idx]->dst)) { + VIR_FREE(disk->name); + if (VIR_STRDUP(disk->name, def->dom->disks[idx]->dst) < 0) + goto cleanup; + } + } + + /* Provide defaults for all remaining disks. */ + ndisks = def->ndisks; + if (VIR_EXPAND_N(def->disks, def->ndisks, + def->dom->ndisks - def->ndisks) < 0) + goto cleanup; + + for (i = 0; i < def->dom->ndisks; i++) { + virDomainCheckpointDiskDefPtr disk; + + if (virBitmapIsBitSet(map, i)) + continue; + disk = &def->disks[ndisks++]; + if (VIR_STRDUP(disk->name, def->dom->disks[i]->dst) < 0) + goto cleanup; + disk->idx = i; + + /* Don't checkpoint empty drives */ + if (virStorageSourceIsEmpty(def->dom->disks[i]->src)) + disk->type = VIR_DOMAIN_CHECKPOINT_TYPE_NONE; + else + disk->type = checkpoint_default; + } + + qsort(&def->disks[0], def->ndisks, sizeof(def->disks[0]), + virDomainCheckpointCompareDiskIndex); + + /* Generate default bitmap names for checkpoint */ + if (virDomainCheckpointDefAssignBitmapNames(def) < 0) + goto cleanup; + + ret = 0; + + cleanup: + virBitmapFree(map); + return ret; +} + +static int +virDomainCheckpointDiskDefFormat(virBufferPtr buf, + virDomainCheckpointDiskDefPtr disk, + unsigned int flags) +{ + if (!disk->name) + return 0; + + virBufferEscapeString(buf, "<disk name='%s'", disk->name); + if (disk->type) + virBufferAsprintf(buf, " checkpoint='%s'", + virDomainCheckpointTypeToString(disk->type)); + if (disk->bitmap) { + virBufferEscapeString(buf, " bitmap='%s'", disk->bitmap); + if (flags & VIR_DOMAIN_CHECKPOINT_XML_SIZE) + virBufferAsprintf(buf, " size='%llu'", disk->size); + } + virBufferAddLit(buf, "/>\n"); + return 0; +} + + +char * +virDomainCheckpointDefFormat(virDomainCheckpointDefPtr def, + virCapsPtr caps, + virDomainXMLOptionPtr xmlopt, + unsigned int flags, + bool internal) +{ + virBuffer buf = VIR_BUFFER_INITIALIZER; + size_t i; + unsigned int domflags = VIR_DOMAIN_DEF_FORMAT_INACTIVE; + + virCheckFlags(VIR_DOMAIN_CHECKPOINT_XML_SECURE | + VIR_DOMAIN_CHECKPOINT_XML_NO_DOMAIN | + VIR_DOMAIN_CHECKPOINT_XML_SIZE, NULL); + if (flags & VIR_DOMAIN_CHECKPOINT_XML_SECURE) + domflags |= VIR_DOMAIN_DEF_FORMAT_SECURE; + + virBufferAddLit(&buf, "<domaincheckpoint>\n"); + virBufferAdjustIndent(&buf, 2); + + virBufferEscapeString(&buf, "<name>%s</name>\n", def->name); + if (def->description) + virBufferEscapeString(&buf, "<description>%s</description>\n", + def->description); + + if (def->parent) { + virBufferAddLit(&buf, "<parent>\n"); + virBufferAdjustIndent(&buf, 2); + virBufferEscapeString(&buf, "<name>%s</name>\n", def->parent); + virBufferAdjustIndent(&buf, -2); + virBufferAddLit(&buf, "</parent>\n"); + } + + virBufferAsprintf(&buf, "<creationTime>%lld</creationTime>\n", + def->creationTime); + + if (def->ndisks) { + virBufferAddLit(&buf, "<disks>\n"); + virBufferAdjustIndent(&buf, 2); + for (i = 0; i < def->ndisks; i++) { + if (virDomainCheckpointDiskDefFormat(&buf, &def->disks[i], + flags) < 0) + goto error; + } + virBufferAdjustIndent(&buf, -2); + virBufferAddLit(&buf, "</disks>\n"); + } + + if (!(flags & VIR_DOMAIN_CHECKPOINT_XML_NO_DOMAIN) && + virDomainDefFormatInternal(def->dom, caps, domflags, &buf, xmlopt) < 0) + goto error; + + if (internal) + virBufferAsprintf(&buf, "<active>%d</active>\n", def->current); + + virBufferAdjustIndent(&buf, -2); + virBufferAddLit(&buf, "</domaincheckpoint>\n"); + + if (virBufferCheckError(&buf) < 0) + return NULL; + + return virBufferContentAndReset(&buf); + + error: + virBufferFreeAndReset(&buf); + return NULL; +} + +/* Checkpoint Obj functions */ +static virDomainCheckpointObjPtr virDomainCheckpointObjNew(void) +{ + virDomainCheckpointObjPtr checkpoint; + + if (VIR_ALLOC(checkpoint) < 0) + return NULL; + + VIR_DEBUG("obj=%p", checkpoint); + + return checkpoint; +} + +static void virDomainCheckpointObjFree(virDomainCheckpointObjPtr checkpoint) +{ + if (!checkpoint) + return; + + VIR_DEBUG("obj=%p", checkpoint); + + virDomainCheckpointDefFree(checkpoint->def); + VIR_FREE(checkpoint); +} + +virDomainCheckpointObjPtr virDomainCheckpointAssignDef(virDomainCheckpointObjListPtr checkpoints, + virDomainCheckpointDefPtr def) +{ + virDomainCheckpointObjPtr chk; + + if (virHashLookup(checkpoints->objs, def->name) != NULL) { + virReportError(VIR_ERR_OPERATION_INVALID, + _("domain checkpoint %s already exists"), + def->name); + return NULL; + } + + if (!(chk = virDomainCheckpointObjNew())) + return NULL; + chk->def = def; + + if (virHashAddEntry(checkpoints->objs, chk->def->name, chk) < 0) { + VIR_FREE(chk); + return NULL; + } + + return chk; +} + +/* Checkpoint Obj List functions */ +static void +virDomainCheckpointObjListDataFree(void *payload, + const void *name ATTRIBUTE_UNUSED) +{ + virDomainCheckpointObjPtr obj = payload; + + virDomainCheckpointObjFree(obj); +} + +virDomainCheckpointObjListPtr +virDomainCheckpointObjListNew(void) +{ + virDomainCheckpointObjListPtr checkpoints; + if (VIR_ALLOC(checkpoints) < 0) + return NULL; + checkpoints->objs = virHashCreate(50, virDomainCheckpointObjListDataFree); + if (!checkpoints->objs) { + VIR_FREE(checkpoints); + return NULL; + } + return checkpoints; +} + +void +virDomainCheckpointObjListFree(virDomainCheckpointObjListPtr checkpoints) +{ + if (!checkpoints) + return; + virHashFree(checkpoints->objs); + VIR_FREE(checkpoints); +} + +struct virDomainCheckpointNameData { + char **const names; + int maxnames; + unsigned int flags; + int count; + bool error; +}; + +static int +virDomainCheckpointObjListCopyNames(void *payload, + const void *name ATTRIBUTE_UNUSED, + void *opaque) +{ + virDomainCheckpointObjPtr obj = payload; + struct virDomainCheckpointNameData *data = opaque; + + if (data->error) + return 0; + /* Caller already sanitized flags. Filtering on DESCENDANTS was + * done by choice of iteration in the caller. */ + if ((data->flags & VIR_DOMAIN_CHECKPOINT_LIST_LEAVES) && obj->nchildren) + return 0; + if ((data->flags & VIR_DOMAIN_CHECKPOINT_LIST_NO_LEAVES) && !obj->nchildren) + return 0; + + if (data->names && data->count < data->maxnames && + VIR_STRDUP(data->names[data->count], obj->def->name) < 0) { + data->error = true; + return 0; + } + data->count++; + return 0; +} + +static int +virDomainCheckpointObjListGetNames(virDomainCheckpointObjListPtr checkpoints, + virDomainCheckpointObjPtr from, + char **const names, int maxnames, + unsigned int flags) +{ + struct virDomainCheckpointNameData data = { names, maxnames, flags, 0, + false }; + size_t i; + + if (!from) { + /* LIST_ROOTS and LIST_DESCENDANTS have the same bit value, + * but opposite semantics. Toggle here to get the correct + * traversal on the metaroot. */ + flags ^= VIR_DOMAIN_CHECKPOINT_LIST_ROOTS; + from = &checkpoints->metaroot; + } + + /* We handle LIST_ROOT/LIST_DESCENDANTS directly, mask that bit + * out to determine when we must use the filter callback. */ + data.flags &= ~VIR_DOMAIN_CHECKPOINT_LIST_DESCENDANTS; + + /* If this common code is being used, we assume that all checkpoints + * have metadata, and thus can handle METADATA up front as an + * all-or-none filter. XXX This might not always be true, if we + * add the ability to track qcow2 bitmaps without the + * use of metadata. */ + if ((data.flags & VIR_DOMAIN_CHECKPOINT_FILTERS_METADATA) == + VIR_DOMAIN_CHECKPOINT_LIST_NO_METADATA) + return 0; + data.flags &= ~VIR_DOMAIN_CHECKPOINT_FILTERS_METADATA; + + /* For ease of coding the visitor, it is easier to zero each group + * where all of the bits are set. */ + if ((data.flags & VIR_DOMAIN_CHECKPOINT_FILTERS_LEAVES) == + VIR_DOMAIN_CHECKPOINT_FILTERS_LEAVES) + data.flags &= ~VIR_DOMAIN_CHECKPOINT_FILTERS_LEAVES; + + if (flags & VIR_DOMAIN_CHECKPOINT_LIST_DESCENDANTS) { + if (from->def) + virDomainCheckpointForEachDescendant(from, + virDomainCheckpointObjListCopyNames, + &data); + else if (names || data.flags) + virHashForEach(checkpoints->objs, + virDomainCheckpointObjListCopyNames, + &data); + else + data.count = virHashSize(checkpoints->objs); + } else if (names || data.flags) { + virDomainCheckpointForEachChild(from, + virDomainCheckpointObjListCopyNames, + &data); + } else { + data.count = from->nchildren; + } + + if (data.error) { + for (i = 0; i < data.count; i++) + VIR_FREE(names[i]); + return -1; + } + + return data.count; +} + +static int +virDomainCheckpointObjListNum(virDomainCheckpointObjListPtr checkpoints, + virDomainCheckpointObjPtr from, + unsigned int flags) +{ + return virDomainCheckpointObjListGetNames(checkpoints, from, NULL, 0, + flags); +} + +virDomainCheckpointObjPtr +virDomainCheckpointFindByName(virDomainCheckpointObjListPtr checkpoints, + const char *name) +{ + return name ? virHashLookup(checkpoints->objs, name) : + &checkpoints->metaroot; +} + +void virDomainCheckpointObjListRemove(virDomainCheckpointObjListPtr checkpoints, + virDomainCheckpointObjPtr checkpoint) +{ + virHashRemoveEntry(checkpoints->objs, checkpoint->def->name); +} + +int +virDomainCheckpointForEach(virDomainCheckpointObjListPtr checkpoints, + virHashIterator iter, + void *data) +{ + return virHashForEach(checkpoints->objs, iter, data); +} + +/* Run iter(data) on all direct children of checkpoint, while ignoring all + * other entries in checkpoints. Return the number of children + * visited. No particular ordering is guaranteed. */ +int +virDomainCheckpointForEachChild(virDomainCheckpointObjPtr checkpoint, + virHashIterator iter, + void *data) +{ + virDomainCheckpointObjPtr child = checkpoint->first_child; + + while (child) { + virDomainCheckpointObjPtr next = child->sibling; + (iter)(child, child->def->name, data); + child = next; + } + + return checkpoint->nchildren; +} + +struct checkpoint_act_on_descendant { + int number; + virHashIterator iter; + void *data; +}; + +static int +virDomainCheckpointActOnDescendant(void *payload, + const void *name, + void *data) +{ + virDomainCheckpointObjPtr obj = payload; + struct checkpoint_act_on_descendant *curr = data; + + curr->number += 1 + virDomainCheckpointForEachDescendant(obj, + curr->iter, + curr->data); + (curr->iter)(payload, name, curr->data); + return 0; +} + +/* Run iter(data) on all descendants of checkpoint, while ignoring all + * other entries in checkpoints. Return the number of descendants + * visited. No particular ordering is guaranteed. */ +int +virDomainCheckpointForEachDescendant(virDomainCheckpointObjPtr checkpoint, + virHashIterator iter, + void *data) +{ + struct checkpoint_act_on_descendant act; + + act.number = 0; + act.iter = iter; + act.data = data; + virDomainCheckpointForEachChild(checkpoint, + virDomainCheckpointActOnDescendant, &act); + + return act.number; +} + +/* Struct and callback function used as a hash table callback; each call + * inspects the pre-existing checkpoint->def->parent field, and adjusts + * the checkpoint->parent field as well as the parent's child fields to + * wire up the hierarchical relations for the given checkpoint. The error + * indicator gets set if a parent is missing or a requested parent would + * cause a circular parent chain. */ +struct checkpoint_set_relation { + virDomainCheckpointObjListPtr checkpoints; + int err; +}; +static int +virDomainCheckpointSetRelations(void *payload, + const void *name ATTRIBUTE_UNUSED, + void *data) +{ + virDomainCheckpointObjPtr obj = payload; + struct checkpoint_set_relation *curr = data; + virDomainCheckpointObjPtr tmp; + + obj->parent = virDomainCheckpointFindByName(curr->checkpoints, + obj->def->parent); + if (!obj->parent) { + curr->err = -1; + obj->parent = &curr->checkpoints->metaroot; + VIR_WARN("checkpoint %s lacks parent", obj->def->name); + } else { + tmp = obj->parent; + while (tmp && tmp->def) { + if (tmp == obj) { + curr->err = -1; + obj->parent = &curr->checkpoints->metaroot; + VIR_WARN("checkpoint %s in circular chain", obj->def->name); + break; + } + tmp = tmp->parent; + } + } + obj->parent->nchildren++; + obj->sibling = obj->parent->first_child; + obj->parent->first_child = obj; + return 0; +} + +/* Populate parent link and child count of all checkpoints, with all + * relations starting as 0/NULL. Return 0 on success, -1 if a parent + * is missing or if a circular relationship was requested. */ +int +virDomainCheckpointUpdateRelations(virDomainCheckpointObjListPtr checkpoints) +{ + struct checkpoint_set_relation act = { checkpoints, 0 }; + + virHashForEach(checkpoints->objs, virDomainCheckpointSetRelations, &act); + return act.err; +} + +/* Prepare to reparent or delete checkpoint, by removing it from its + * current listed parent. Note that when bulk removing all children + * of a parent, it is faster to just 0 the count rather than calling + * this function on each child. */ +void +virDomainCheckpointDropParent(virDomainCheckpointObjPtr checkpoint) +{ + virDomainCheckpointObjPtr prev = NULL; + virDomainCheckpointObjPtr curr = NULL; + + checkpoint->parent->nchildren--; + curr = checkpoint->parent->first_child; + while (curr != checkpoint) { + if (!curr) { + VIR_WARN("inconsistent checkpoint relations"); + return; + } + prev = curr; + curr = curr->sibling; + } + if (prev) + prev->sibling = checkpoint->sibling; + else + checkpoint->parent->first_child = checkpoint->sibling; + checkpoint->parent = NULL; + checkpoint->sibling = NULL; +} + +int +virDomainListAllCheckpoints(virDomainCheckpointObjListPtr checkpoints, + virDomainCheckpointObjPtr from, + virDomainPtr dom, + virDomainCheckpointPtr **chks, + unsigned int flags) +{ + int count = virDomainCheckpointObjListNum(checkpoints, from, flags); + virDomainCheckpointPtr *list = NULL; + char **names; + int ret = -1; + size_t i; + + if (!chks || count < 0) + return count; + if (VIR_ALLOC_N(names, count) < 0 || + VIR_ALLOC_N(list, count + 1) < 0) + goto cleanup; + + if (virDomainCheckpointObjListGetNames(checkpoints, from, names, count, + flags) < 0) + goto cleanup; + for (i = 0; i < count; i++) + if ((list[i] = virGetDomainCheckpoint(dom, names[i])) == NULL) + goto cleanup; + + ret = count; + *chks = list; + + cleanup: + for (i = 0; i < count; i++) + VIR_FREE(names[i]); + VIR_FREE(names); + if (ret < 0 && list) { + for (i = 0; i < count; i++) + virObjectUnref(list[i]); + VIR_FREE(list); + } + return ret; +} + + +int +virDomainCheckpointRedefinePrep(virDomainPtr domain, + virDomainObjPtr vm, + virDomainCheckpointDefPtr *defptr, + virDomainCheckpointObjPtr *chk, + virDomainXMLOptionPtr xmlopt, + bool *update_current) +{ + virDomainCheckpointDefPtr def = *defptr; + int ret = -1; + char uuidstr[VIR_UUID_STRING_BUFLEN]; + virDomainCheckpointObjPtr other; + + virUUIDFormat(domain->uuid, uuidstr); + + /* Prevent circular chains */ + if (def->parent) { + if (STREQ(def->name, def->parent)) { + virReportError(VIR_ERR_INVALID_ARG, + _("cannot set checkpoint %s as its own parent"), + def->name); + goto cleanup; + } + other = virDomainCheckpointFindByName(vm->checkpoints, def->parent); + if (!other) { + virReportError(VIR_ERR_INVALID_ARG, + _("parent %s for checkpoint %s not found"), + def->parent, def->name); + goto cleanup; + } + while (other->def->parent) { + if (STREQ(other->def->parent, def->name)) { + virReportError(VIR_ERR_INVALID_ARG, + _("parent %s would create cycle to %s"), + other->def->name, def->name); + goto cleanup; + } + other = virDomainCheckpointFindByName(vm->checkpoints, + other->def->parent); + if (!other) { + VIR_WARN("checkpoints are inconsistent for %s", + vm->def->name); + break; + } + } + } + + if (def->dom && + memcmp(def->dom->uuid, domain->uuid, VIR_UUID_BUFLEN)) { + virReportError(VIR_ERR_INVALID_ARG, + _("definition for checkpoint %s must use uuid %s"), + def->name, uuidstr); + goto cleanup; + } + + other = virDomainCheckpointFindByName(vm->checkpoints, def->name); + if (other) { + if (other->def->dom) { + if (def->dom) { + if (!virDomainDefCheckABIStability(other->def->dom, + def->dom, xmlopt)) + goto cleanup; + } else { + /* Transfer the domain def */ + def->dom = other->def->dom; + other->def->dom = NULL; + } + } + + if (def->dom) { + if (virDomainCheckpointAlignDisks(def) < 0) { + /* revert stealing of the checkpoint domain definition */ + if (def->dom && !other->def->dom) { + other->def->dom = def->dom; + def->dom = NULL; + } + goto cleanup; + } + } + + if (other == vm->current_checkpoint) { + *update_current = true; + vm->current_checkpoint = NULL; + } + + /* Drop and rebuild the parent relationship, but keep all + * child relations by reusing chk. */ + virDomainCheckpointDropParent(other); + virDomainCheckpointDefFree(other->def); + other->def = def; + *defptr = NULL; + *chk = other; + } else if (def->dom && virDomainCheckpointAlignDisks(def) < 0) { + goto cleanup; + } + + ret = 0; + cleanup: + return ret; +} diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index fa3db9266f..25fc4af450 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -1,7 +1,7 @@ /* * domain_conf.c: domain XML processing * - * Copyright (C) 2006-2016 Red Hat, Inc. + * Copyright (C) 2006-2018 Red Hat, Inc. * Copyright (C) 2006-2008 Daniel P. Berrange * Copyright (c) 2015 SUSE LINUX Products GmbH, Nuernberg, Germany. * @@ -29,6 +29,7 @@ #include "configmake.h" #include "internal.h" #include "virerror.h" +#include "checkpoint_conf.h" #include "datatypes.h" #include "domain_addr.h" #include "domain_conf.h" @@ -3313,6 +3314,7 @@ static void virDomainObjDispose(void *obj) (dom->privateDataFreeFunc)(dom->privateData); virDomainSnapshotObjListFree(dom->snapshots); + virDomainCheckpointObjListFree(dom->checkpoints); } virDomainObjPtr @@ -3342,6 +3344,9 @@ virDomainObjNew(virDomainXMLOptionPtr xmlopt) if (!(domain->snapshots = virDomainSnapshotObjListNew())) goto error; + if (!(domain->checkpoints = virDomainCheckpointObjListNew())) + goto error; + virObjectLock(domain); virDomainObjSetState(domain, VIR_DOMAIN_SHUTOFF, VIR_DOMAIN_SHUTOFF_UNKNOWN); diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 5e22acb059..fbe7ba2d40 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -68,6 +68,27 @@ virCapabilitiesSetHostCPU; virCapabilitiesSetNetPrefix; +# conf/checkpoint_conf.h +virDomainCheckpointAlignDisks; +virDomainCheckpointAssignDef; +virDomainCheckpointDefFormat; +virDomainCheckpointDefFree; +virDomainCheckpointDefParseString; +virDomainCheckpointDropParent; +virDomainCheckpointFindByName; +virDomainCheckpointForEach; +virDomainCheckpointForEachChild; +virDomainCheckpointForEachDescendant; +virDomainCheckpointObjListFree; +virDomainCheckpointObjListNew; +virDomainCheckpointObjListRemove; +virDomainCheckpointRedefinePrep; +virDomainCheckpointTypeFromString; +virDomainCheckpointTypeToString; +virDomainCheckpointUpdateRelations; +virDomainListAllCheckpoints; + + # conf/cpu_conf.h virCPUCacheModeTypeFromString; virCPUCacheModeTypeToString; diff --git a/tests/Makefile.am b/tests/Makefile.am index 9b835fa369..87a4b3632b 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -288,7 +288,7 @@ endif WITH_LIBXL if WITH_QEMU test_programs += qemuxml2argvtest qemuxml2xmltest \ - qemuargv2xmltest domainsnapshotxml2xmltest \ + qemuargv2xmltest domaincheckpointxml2xmltest domainsnapshotxml2xmltest \ qemumonitorjsontest qemuhotplugtest \ qemuagenttest qemucapabilitiestest qemucaps2xmltest \ qemumemlocktest \ @@ -673,6 +673,11 @@ qemublocktest_LDADD = \ $(LDADDS) \ $(NULL) +domaincheckpointxml2xmltest_SOURCES = \ + domaincheckpointxml2xmltest.c testutilsqemu.c testutilsqemu.h \ + testutils.c testutils.h +domaincheckpointxml2xmltest_LDADD = $(qemu_LDADDS) $(LDADDS) + domainsnapshotxml2xmltest_SOURCES = \ domainsnapshotxml2xmltest.c testutilsqemu.c testutilsqemu.h \ testutils.c testutils.h @@ -701,7 +706,7 @@ qemusecuritytest_LDADD = $(qemu_LDADDS) $(LDADDS) else ! WITH_QEMU EXTRA_DIST += qemuxml2argvtest.c qemuxml2xmltest.c qemuargv2xmltest.c \ - domainsnapshotxml2xmltest.c \ + domaincheckpointxml2xmltest.c domainsnapshotxml2xmltest.c \ testutilsqemu.c testutilsqemu.h \ testutilsqemuschema.c testutilsqemuschema.h \ qemumonitorjsontest.c qemuhotplugtest.c \ diff --git a/tests/domaincheckpointxml2xmltest.c b/tests/domaincheckpointxml2xmltest.c new file mode 100644 index 0000000000..5381b6352b --- /dev/null +++ b/tests/domaincheckpointxml2xmltest.c @@ -0,0 +1,231 @@ +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> + +#include <sys/types.h> +#include <fcntl.h> + +#include <regex.h> + +#include "testutils.h" + +#ifdef WITH_QEMU + +# include "internal.h" +# include "qemu/qemu_conf.h" +# include "qemu/qemu_domain.h" +# include "checkpoint_conf.h" +# include "testutilsqemu.h" +# include "virstring.h" + +# define VIR_FROM_THIS VIR_FROM_NONE + +static virQEMUDriver driver; + +/* This regex will skip the following XML constructs in test files + * that are dynamically generated and thus problematic to test: + * <name>1234352345</name> if the checkpoint has no name, + * <creationTime>23523452345</creationTime>. + */ +static const char *testCheckpointXMLVariableLineRegexStr = + "<(name|creationTime)>[0-9]+</(name|creationTime)>"; + +regex_t *testCheckpointXMLVariableLineRegex = NULL; + +static char * +testFilterXML(char *xml) +{ + virBuffer buf = VIR_BUFFER_INITIALIZER; + char **xmlLines = NULL; + char **xmlLine; + char *ret = NULL; + + if (!(xmlLines = virStringSplit(xml, "\n", 0))) { + VIR_FREE(xml); + goto cleanup; + } + VIR_FREE(xml); + + for (xmlLine = xmlLines; *xmlLine; xmlLine++) { + if (regexec(testCheckpointXMLVariableLineRegex, + *xmlLine, 0, NULL, 0) == 0) + continue; + + virBufferStrcat(&buf, *xmlLine, "\n", NULL); + } + + if (virBufferCheckError(&buf) < 0) + goto cleanup; + + ret = virBufferContentAndReset(&buf); + + cleanup: + virBufferFreeAndReset(&buf); + virStringListFree(xmlLines); + return ret; +} + +static int +testCompareXMLToXMLFiles(const char *inxml, + const char *outxml, + bool internal, + bool redefine) +{ + char *inXmlData = NULL; + char *outXmlData = NULL; + char *actual = NULL; + int ret = -1; + virDomainCheckpointDefPtr def = NULL; + unsigned int flags = VIR_DOMAIN_CHECKPOINT_PARSE_DISKS; + + if (internal) + flags |= VIR_DOMAIN_CHECKPOINT_PARSE_INTERNAL; + + if (redefine) + flags |= VIR_DOMAIN_CHECKPOINT_PARSE_REDEFINE; + + if (virTestLoadFile(inxml, &inXmlData) < 0) + goto cleanup; + + if (virTestLoadFile(outxml, &outXmlData) < 0) + goto cleanup; + + if (!(def = virDomainCheckpointDefParseString(inXmlData, driver.caps, + driver.xmlopt, + flags))) + goto cleanup; + + /* Parsing XML does not populate the domain definition, so add a + * canned bare-bones fallback */ + if (!def->dom) { + // HACK + ret = 77; + goto cleanup; + const char *def_dom = "" + "<domain type='qemu'>" + " <name>fedora</name>" + " <uuid>93a5c045-6457-2c09-e56c-927cdf34e178</uuid>" +/* arch='x86_64' machine='pc'*/ + " <os><type>hvm</type></os>" + "</domain>"; + int dom_flags = VIR_DOMAIN_DEF_PARSE_INACTIVE; + if (!(def->dom = virDomainDefParseString(def_dom, driver.caps, + driver.xmlopt, NULL, + dom_flags))) + goto cleanup; + } + + if (!(actual = virDomainCheckpointDefFormat(def, driver.caps, + driver.xmlopt, + VIR_DOMAIN_DEF_FORMAT_SECURE, + internal))) + goto cleanup; + + if (!redefine) { + if (!(actual = testFilterXML(actual))) + goto cleanup; + + if (!(outXmlData = testFilterXML(outXmlData))) + goto cleanup; + } + + if (STRNEQ(outXmlData, actual)) { + virTestDifferenceFull(stderr, outXmlData, outxml, actual, inxml); + goto cleanup; + } + + ret = 0; + + cleanup: + VIR_FREE(inXmlData); + VIR_FREE(outXmlData); + VIR_FREE(actual); + virDomainCheckpointDefFree(def); + return ret; +} + +struct testInfo { + const char *inxml; + const char *outxml; + bool internal; + bool redefine; +}; + + +static int +testCompareXMLToXMLHelper(const void *data) +{ + const struct testInfo *info = data; + + return testCompareXMLToXMLFiles(info->inxml, info->outxml, + info->internal, info->redefine); +} + + +static int +mymain(void) +{ + int ret = 0; + + if (qemuTestDriverInit(&driver) < 0) + return EXIT_FAILURE; + + if (VIR_ALLOC(testCheckpointXMLVariableLineRegex) < 0) + goto cleanup; + + if (regcomp(testCheckpointXMLVariableLineRegex, + testCheckpointXMLVariableLineRegexStr, + REG_EXTENDED | REG_NOSUB) != 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "failed to compile test regex"); + goto cleanup; + } + + +# define DO_TEST(prefix, name, inpath, outpath, internal, redefine) \ + do { \ + const struct testInfo info = {abs_srcdir "/" inpath "/" name ".xml", \ + abs_srcdir "/" outpath "/" name ".xml", \ + internal, redefine}; \ + if (virTestRun("CHECKPOINT XML-2-XML " prefix " " name, \ + testCompareXMLToXMLHelper, &info) < 0) \ + ret = -1; \ + } while (0) + +# define DO_TEST_INOUT(name, internal, redefine) \ + DO_TEST("in->out", name,\ + "domaincheckpointxml2xmlin",\ + "domaincheckpointxml2xmlout",\ + internal, redefine) + + /* Unset or set all envvars here that are copied in qemudBuildCommandLine + * using ADD_ENV_COPY, otherwise these tests may fail due to unexpected + * values for these envvars */ + setenv("PATH", "/bin", 1); + + DO_TEST_INOUT("empty", false, false); + DO_TEST_INOUT("sample", false, false); + + cleanup: + if (testCheckpointXMLVariableLineRegex) + regfree(testCheckpointXMLVariableLineRegex); + VIR_FREE(testCheckpointXMLVariableLineRegex); + qemuTestDriverFree(&driver); + + return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE; +} + +VIR_TEST_MAIN(mymain) + +#else + +int +main(void) +{ + return EXIT_AM_SKIP; +} + +#endif /* WITH_QEMU */ -- 2.20.1 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list