From nobody Fri Oct 18 06:19:04 2024 Delivered-To: importer@patchew.org Received-SPF: pass (zoho.com: domain of redhat.com designates 209.132.183.28 as permitted sender) client-ip=209.132.183.28; envelope-from=libvir-list-bounces@redhat.com; helo=mx1.redhat.com; Authentication-Results: mx.zohomail.com; spf=pass (zoho.com: domain of redhat.com designates 209.132.183.28 as permitted sender) smtp.mailfrom=libvir-list-bounces@redhat.com; dmarc=pass(p=none dis=none) header.from=redhat.com Return-Path: Received: from mx1.redhat.com (mx1.redhat.com [209.132.183.28]) by mx.zohomail.com with SMTPS id 15404968861561019.0426582524859; Thu, 25 Oct 2018 12:48:06 -0700 (PDT) Received: from smtp.corp.redhat.com (int-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.12]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id A8D1930224B2; Thu, 25 Oct 2018 19:48:03 +0000 (UTC) Received: from colo-mx.corp.redhat.com (colo-mx01.intmail.prod.int.phx2.redhat.com [10.5.11.20]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 5C94260F97; Thu, 25 Oct 2018 19:48:03 +0000 (UTC) Received: from lists01.pubmisc.prod.ext.phx2.redhat.com (lists01.pubmisc.prod.ext.phx2.redhat.com [10.5.19.33]) by colo-mx.corp.redhat.com (Postfix) with ESMTP id 082CA180B5B7; Thu, 25 Oct 2018 19:48:03 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.12]) by lists01.pubmisc.prod.ext.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id w9PJL08M027718 for ; Thu, 25 Oct 2018 15:21:00 -0400 Received: by smtp.corp.redhat.com (Postfix) id 2D17960CD8; Thu, 25 Oct 2018 19:21:00 +0000 (UTC) Received: from red.redhat.com (ovpn-122-116.rdu2.redhat.com [10.10.122.116]) by smtp.corp.redhat.com (Postfix) with ESMTP id 6F34965F48; Thu, 25 Oct 2018 19:20:58 +0000 (UTC) From: Eric Blake To: libvir-list@redhat.com Date: Thu, 25 Oct 2018 20:20:12 +0100 Message-Id: <20181025192021.350438-12-eblake@redhat.com> In-Reply-To: <20181025192021.350438-1-eblake@redhat.com> References: <20181025192021.350438-1-eblake@redhat.com> X-Scanned-By: MIMEDefang 2.79 on 10.5.11.12 X-loop: libvir-list@redhat.com Cc: amureini@redhat.com, derez@redhat.com, vsementsov@virtuozzo.com, ydary@redhat.com, nsoffer@redhat.com, jsnow@redhat.com Subject: [libvirt] [PATCH v3 11/20] wip: backup: Parse and output checkpoint XML X-BeenThere: libvir-list@redhat.com X-Mailman-Version: 2.1.12 Precedence: junk List-Id: Development discussions about the libvirt library & tools List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Sender: libvir-list-bounces@redhat.com Errors-To: libvir-list-bounces@redhat.com X-Scanned-By: MIMEDefang 2.79 on 10.5.11.12 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.49]); Thu, 25 Oct 2018 19:48:04 +0000 (UTC) Content-Type: text/plain; charset="utf-8" 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 element, or just use --no-domain flag). Signed-off-by: Eric Blake --- src/conf/checkpoint_conf.h | 152 ++++ src/conf/domain_conf.h | 11 +- po/POTFILES | 1 + src/conf/Makefile.inc.am | 2 + src/conf/checkpoint_conf.c | 1040 +++++++++++++++++++++++++++ src/conf/domain_conf.c | 7 +- src/libvirt_private.syms | 21 + tests/Makefile.am | 9 +- tests/domaincheckpointxml2xmltest.c | 231 ++++++ 9 files changed, 1470 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..16dac6df5f --- /dev/null +++ b/src/conf/checkpoint_conf.h @@ -0,0 +1,152 @@ +/* + * checkpoint_conf.h: domain checkpoint XML processing + * + * Copyright (C) 2006-2018 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 + * . + * + * Author: Eric Blake + */ + +#ifndef __CHECKPOINT_CONF_H +# define __CHECKPOINT_CONF_H + +# include "internal.h" +# include "domain_conf.h" + +/* Items related to checkpoint state */ + +typedef enum { + VIR_DOMAIN_CHECKPOINT_TYPE_DEFAULT =3D 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 dom->disks that matches na= me */ + 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 s= et */ +}; + +struct _virDomainCheckpointObj { + virDomainCheckpointDefPtr def; /* non-NULL except for metaroot */ + + virDomainCheckpointObjPtr parent; /* non-NULL except for metaroot, bef= ore + virDomainCheckpointUpdateRelation= s, or + after virDomainCheckpointDropPare= nt */ + virDomainCheckpointObjPtr sibling; /* NULL if last child of parent */ + size_t nchildren; + virDomainCheckpointObjPtr first_child; /* NULL if no children */ +}; + +virDomainCheckpointObjListPtr virDomainCheckpointObjListNew(void); +void virDomainCheckpointObjListFree(virDomainCheckpointObjListPtr checkpoi= nts); + +typedef enum { + VIR_DOMAIN_CHECKPOINT_PARSE_REDEFINE =3D 1 << 0, + VIR_DOMAIN_CHECKPOINT_PARSE_DISKS =3D 1 << 1, + VIR_DOMAIN_CHECKPOINT_PARSE_INTERNAL =3D 1 << 2, +} virDomainCheckpointParseFlags; + +virDomainCheckpointDefPtr virDomainCheckpointDefParseString(const char *xm= lStr, + virCapsPtr cap= s, + virDomainXMLOp= tionPtr xmlopt, + unsigned int f= lags); +virDomainCheckpointDefPtr virDomainCheckpointDefParseNode(xmlDocPtr xml, + xmlNodePtr root, + virCapsPtr caps, + virDomainXMLOpti= onPtr xmlopt, + unsigned int fla= gs); +void virDomainCheckpointDefFree(virDomainCheckpointDefPtr def); +char *virDomainCheckpointDefFormat(virDomainCheckpointDefPtr def, + virCapsPtr caps, + virDomainXMLOptionPtr xmlopt, + unsigned int flags, + bool internal); +int virDomainCheckpointAlignDisks(virDomainCheckpointDefPtr checkpoint); +virDomainCheckpointObjPtr virDomainCheckpointAssignDef(virDomainCheckpoint= ObjListPtr checkpoints, + virDomainCheckpoint= DefPtr def); + +virDomainCheckpointObjPtr virDomainCheckpointFindByName(virDomainCheckpoin= tObjListPtr checkpoints, + const char *name); +void virDomainCheckpointObjListRemove(virDomainCheckpointObjListPtr checkp= oints, + virDomainCheckpointObjPtr checkpoint= ); +int virDomainCheckpointForEach(virDomainCheckpointObjListPtr checkpoints, + virHashIterator iter, + void *data); +int virDomainCheckpointForEachChild(virDomainCheckpointObjPtr checkpoint, + virHashIterator iter, + void *data); +int virDomainCheckpointForEachDescendant(virDomainCheckpointObjPtr checkpo= int, + virHashIterator iter, + void *data); +int virDomainCheckpointUpdateRelations(virDomainCheckpointObjListPtr check= points); +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 /* __CHECKPOINT_CONF_H */ diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index 785786c9b9..3536903f28 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. * @@ -121,6 +121,12 @@ typedef virDomainMemballoonDef *virDomainMemballoonDef= Ptr; 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; @@ -2609,6 +2615,9 @@ struct _virDomainObj { bool hasManagedSave; + virDomainCheckpointObjListPtr checkpoints; + virDomainCheckpointObjPtr current_checkpoint; + void *privateData; void (*privateDataFreeFunc)(void *); diff --git a/po/POTFILES b/po/POTFILES index d246657188..0a2845c4c6 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 af23810640..985389b584 100644 --- a/src/conf/Makefile.inc.am +++ b/src/conf/Makefile.inc.am @@ -10,6 +10,8 @@ NETDEV_CONF_SOURCES =3D \ DOMAIN_CONF_SOURCES =3D \ 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..7d089368ec --- /dev/null +++ b/src/conf/checkpoint_conf.c @@ -0,0 +1,1040 @@ +/* + * checkpoint_conf.c: domain checkpoint XML processing + * + * Copyright (C) 2006-2018 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 + * . + * + * Author: Eric Blake + */ + +#include + +#include +#include +#include +#include + +#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 checkpo= ints */ +}; + +/* Checkpoint Def functions */ +static void +virDomainCheckpointDiskDefClear(virDomainCheckpointDiskDefPtr disk) +{ + VIR_FREE(disk->name); + VIR_FREE(disk->node); + 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 =3D 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, + unsigned int flags, + virDomainCheckpointDiskDefPtr def) +{ + int ret =3D -1; + char *checkpoint =3D NULL; + char *bitmap =3D NULL; + xmlNodePtr saved =3D ctxt->node; + + ctxt->node =3D node; + + def->name =3D virXMLPropString(node, "name"); + if (!def->name) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing name from disk checkpoint element")); + goto cleanup; + } + if (flags & VIR_DOMAIN_CHECKPOINT_PARSE_INTERNAL) + def->node =3D virXMLPropString(node, "node"); + + checkpoint =3D virXMLPropString(node, "checkpoint"); + if (checkpoint) { + def->type =3D virDomainCheckpointTypeFromString(checkpoint); + if (def->type <=3D 0) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("unknown disk checkpoint setting '%s'"), + checkpoint); + goto cleanup; + } + } else { + def->type =3D VIR_DOMAIN_CHECKPOINT_TYPE_BITMAP; + } + + bitmap =3D virXMLPropString(node, "bitmap"); + if (bitmap) { + if (def->type !=3D VIR_DOMAIN_CHECKPOINT_TYPE_BITMAP) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("disk checkpoint bitmap '%s' requires " + "type=3D'bitmap'"), + bitmap); + goto cleanup; + } + VIR_STEAL_PTR(def->bitmap, bitmap); + } + + ret =3D 0; + cleanup: + ctxt->node =3D 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 =3D NULL; + virDomainCheckpointDefPtr ret =3D NULL; + xmlNodePtr *nodes =3D NULL; + size_t i; + int n; + char *creation =3D NULL; + struct timeval tv; + int active; + char *tmp; + + if (VIR_ALLOC(def) < 0) + goto cleanup; + + gettimeofday(&tv, NULL); + + def->name =3D virXPathString("string(./name)", ctxt); + if (def->name =3D=3D 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 =3D 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 checkpoin= t")); + goto cleanup; + } + + def->parent =3D virXPathString("string(./parent/name)", ctxt); + + if ((tmp =3D virXPathString("string(./domain/@type)", ctxt))) { + int domainflags =3D VIR_DOMAIN_DEF_PARSE_INACTIVE | + VIR_DOMAIN_DEF_PARSE_SKIP_VALIDATE; + xmlNodePtr domainNode =3D virXPathNode("./domain", ctxt); + + VIR_FREE(tmp); + if (!domainNode) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing domain in checkpoint")); + goto cleanup; + } + def->dom =3D virDomainDefParseNode(ctxt->node->doc, domainNode, + caps, xmlopt, NULL, domainfla= gs); + if (!def->dom) + goto cleanup; + } else { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing domain in checkpoint redefine")); + goto cleanup; + } + } else { + def->creationTime =3D tv.tv_sec; + } + + if ((n =3D 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 =3D n; + for (i =3D 0; i < def->ndisks; i++) { + if (virDomainCheckpointDiskDefParseXML(nodes[i], ctxt, flags, + &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 =3D active !=3D 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 =3D NULL; + virDomainCheckpointDefPtr def =3D NULL; + + if (!virXMLNodeNameEqual(root, "domaincheckpoint")) { + virReportError(VIR_ERR_XML_ERROR, "%s", _("domaincheckpoint")); + goto cleanup; + } + + ctxt =3D xmlXPathNewContext(xml); + if (ctxt =3D=3D NULL) { + virReportOOMError(); + goto cleanup; + } + + ctxt->node =3D root; + def =3D virDomainCheckpointDefParse(ctxt, caps, xmlopt, flags); + cleanup: + xmlXPathFreeContext(ctxt); + return def; +} + +virDomainCheckpointDefPtr +virDomainCheckpointDefParseString(const char *xmlStr, + virCapsPtr caps, + virDomainXMLOptionPtr xmlopt, + unsigned int flags) +{ + virDomainCheckpointDefPtr ret =3D NULL; + xmlDocPtr xml; + int keepBlanksDefault =3D xmlKeepBlanksDefault(0); + + if ((xml =3D virXMLParse(NULL, xmlStr, _("(domain_checkpoint)")))) { + xmlKeepBlanksDefault(keepBlanksDefault); + ret =3D 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 =3D 0; i < def->ndisks; i++) { + virDomainCheckpointDiskDefPtr disk =3D &def->disks[i]; + + if (disk->type !=3D 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 =3D a; + const virDomainCheckpointDiskDef *diskb =3D 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 =3D -1; + virBitmapPtr map =3D NULL; + size_t i; + int ndisks; + int checkpoint_default =3D 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 omitted, do bitmap on all disks; otherwise, do nothing + * for omitted disks */ + if (!def->ndisks) + checkpoint_default =3D VIR_DOMAIN_CHECKPOINT_TYPE_BITMAP; + + if (!(map =3D virBitmapNew(def->dom->ndisks))) + goto cleanup; + + /* Double check requested disks. */ + for (i =3D 0; i < def->ndisks; i++) { + virDomainCheckpointDiskDefPtr disk =3D &def->disks[i]; + int idx =3D 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 =3D 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 =3D def->ndisks; + if (VIR_EXPAND_N(def->disks, def->ndisks, + def->dom->ndisks - def->ndisks) < 0) + goto cleanup; + + for (i =3D 0; i < def->dom->ndisks; i++) { + virDomainCheckpointDiskDefPtr disk; + + if (virBitmapIsBitSet(map, i)) + continue; + disk =3D &def->disks[ndisks++]; + if (VIR_STRDUP(disk->name, def->dom->disks[i]->dst) < 0) + goto cleanup; + disk->idx =3D i; + + /* Don't checkpoint empty drives */ + if (virStorageSourceIsEmpty(def->dom->disks[i]->src)) + disk->type =3D VIR_DOMAIN_CHECKPOINT_TYPE_NONE; + else + disk->type =3D 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 =3D 0; + + cleanup: + virBitmapFree(map); + return ret; +} + +static int +virDomainCheckpointDiskDefFormat(virBufferPtr buf, + virDomainCheckpointDiskDefPtr disk, + unsigned int flags, bool internal) +{ + if (!disk->name) + return 0; + + virBufferEscapeString(buf, "name); + if (internal) + virBufferEscapeString(buf, " node=3D'%s'", disk->node); + if (disk->type) + virBufferAsprintf(buf, " checkpoint=3D'%s'", + virDomainCheckpointTypeToString(disk->type)); + if (disk->bitmap) { + virBufferEscapeString(buf, " bitmap=3D'%s'", disk->bitmap); + if (flags & VIR_DOMAIN_CHECKPOINT_XML_SIZE) + virBufferAsprintf(buf, " size=3D'%llu'", disk->size); + } + virBufferAddLit(buf, "/>\n"); + return 0; +} + + +char * +virDomainCheckpointDefFormat(virDomainCheckpointDefPtr def, + virCapsPtr caps, + virDomainXMLOptionPtr xmlopt, + unsigned int flags, + bool internal) +{ + virBuffer buf =3D VIR_BUFFER_INITIALIZER; + size_t i; + unsigned int domflags =3D 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 |=3D VIR_DOMAIN_DEF_FORMAT_SECURE; + + virBufferAddLit(&buf, "\n"); + virBufferAdjustIndent(&buf, 2); + + virBufferEscapeString(&buf, "%s\n", def->name); + if (def->description) + virBufferEscapeString(&buf, "%s\n", + def->description); + + if (def->parent) { + virBufferAddLit(&buf, "\n"); + virBufferAdjustIndent(&buf, 2); + virBufferEscapeString(&buf, "%s\n", def->parent); + virBufferAdjustIndent(&buf, -2); + virBufferAddLit(&buf, "\n"); + } + + virBufferAsprintf(&buf, "%lld\n", + def->creationTime); + + if (def->ndisks) { + virBufferAddLit(&buf, "\n"); + virBufferAdjustIndent(&buf, 2); + for (i =3D 0; i < def->ndisks; i++) { + if (virDomainCheckpointDiskDefFormat(&buf, &def->disks[i], + flags, internal) < 0) + goto error; + } + virBufferAdjustIndent(&buf, -2); + virBufferAddLit(&buf, "\n"); + } + + if (!(flags & VIR_DOMAIN_CHECKPOINT_XML_NO_DOMAIN) && + virDomainDefFormatInternal(def->dom, caps, domflags, &buf, xmlopt)= < 0) + goto error; + + if (internal) + virBufferAsprintf(&buf, "%d\n", def->current); + + virBufferAdjustIndent(&buf, -2); + virBufferAddLit(&buf, "\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=3D%p", checkpoint); + + return checkpoint; +} + +static void virDomainCheckpointObjFree(virDomainCheckpointObjPtr checkpoin= t) +{ + if (!checkpoint) + return; + + VIR_DEBUG("obj=3D%p", checkpoint); + + virDomainCheckpointDefFree(checkpoint->def); + VIR_FREE(checkpoint); +} + +virDomainCheckpointObjPtr virDomainCheckpointAssignDef(virDomainCheckpoint= ObjListPtr checkpoints, + virDomainCheckpoint= DefPtr def) +{ + virDomainCheckpointObjPtr chk; + + if (virHashLookup(checkpoints->objs, def->name) !=3D NULL) { + virReportError(VIR_ERR_OPERATION_INVALID, + _("domain checkpoint %s already exists"), + def->name); + return NULL; + } + + if (!(chk =3D virDomainCheckpointObjNew())) + return NULL; + chk->def =3D 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 =3D payload; + + virDomainCheckpointObjFree(obj); +} + +virDomainCheckpointObjListPtr +virDomainCheckpointObjListNew(void) +{ + virDomainCheckpointObjListPtr checkpoints; + if (VIR_ALLOC(checkpoints) < 0) + return NULL; + checkpoints->objs =3D virHashCreate(50, virDomainCheckpointObjListData= Free); + 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 =3D payload; + struct virDomainCheckpointNameData *data =3D 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->nchildre= n) + return 0; + if ((data->flags & VIR_DOMAIN_CHECKPOINT_LIST_NO_LEAVES) && !obj->nchi= ldren) + return 0; + + if (data->names && data->count < data->maxnames && + VIR_STRDUP(data->names[data->count], obj->def->name) < 0) { + data->error =3D true; + return 0; + } + data->count++; + return 0; +} + +static int +virDomainCheckpointObjListGetNames(virDomainCheckpointObjListPtr checkpoin= ts, + virDomainCheckpointObjPtr from, + char **const names, int maxnames, + unsigned int flags) +{ + struct virDomainCheckpointNameData data =3D { 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 ^=3D VIR_DOMAIN_CHECKPOINT_LIST_ROOTS; + from =3D &checkpoints->metaroot; + } + + /* We handle LIST_ROOT/LIST_DESCENDANTS directly, mask that bit + * out to determine when we must use the filter callback. */ + data.flags &=3D ~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) =3D=3D + VIR_DOMAIN_CHECKPOINT_LIST_NO_METADATA) + return 0; + data.flags &=3D ~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) =3D=3D + VIR_DOMAIN_CHECKPOINT_FILTERS_LEAVES) + data.flags &=3D ~VIR_DOMAIN_CHECKPOINT_FILTERS_LEAVES; + + if (flags & VIR_DOMAIN_CHECKPOINT_LIST_DESCENDANTS) { + if (from->def) + virDomainCheckpointForEachDescendant(from, + virDomainCheckpointObjLis= tCopyNames, + &data); + else if (names || data.flags) + virHashForEach(checkpoints->objs, + virDomainCheckpointObjListCopyNames, + &data); + else + data.count =3D virHashSize(checkpoints->objs); + } else if (names || data.flags) { + virDomainCheckpointForEachChild(from, + virDomainCheckpointObjListCopyName= s, + &data); + } else { + data.count =3D from->nchildren; + } + + if (data.error) { + for (i =3D 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 checkp= oints, + 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 =3D checkpoint->first_child; + + while (child) { + virDomainCheckpointObjPtr next =3D child->sibling; + (iter)(child, child->def->name, data); + child =3D 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 =3D payload; + struct checkpoint_act_on_descendant *curr =3D data; + + curr->number +=3D 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 =3D 0; + act.iter =3D iter; + act.data =3D data; + virDomainCheckpointForEachChild(checkpoint, + virDomainCheckpointActOnDescendant, &a= ct); + + 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 =3D payload; + struct checkpoint_set_relation *curr =3D data; + virDomainCheckpointObjPtr tmp; + + obj->parent =3D virDomainCheckpointFindByName(curr->checkpoints, + obj->def->parent); + if (!obj->parent) { + curr->err =3D -1; + obj->parent =3D &curr->checkpoints->metaroot; + VIR_WARN("checkpoint %s lacks parent", obj->def->name); + } else { + tmp =3D obj->parent; + while (tmp && tmp->def) { + if (tmp =3D=3D obj) { + curr->err =3D -1; + obj->parent =3D &curr->checkpoints->metaroot; + VIR_WARN("checkpoint %s in circular chain", obj->def->name= ); + break; + } + tmp =3D tmp->parent; + } + } + obj->parent->nchildren++; + obj->sibling =3D obj->parent->first_child; + obj->parent->first_child =3D 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 checkpoin= ts) +{ + struct checkpoint_set_relation act =3D { checkpoints, 0 }; + + virHashForEach(checkpoints->objs, virDomainCheckpointSetRelations, &ac= t); + 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 =3D NULL; + virDomainCheckpointObjPtr curr =3D NULL; + + checkpoint->parent->nchildren--; + curr =3D checkpoint->parent->first_child; + while (curr !=3D checkpoint) { + if (!curr) { + VIR_WARN("inconsistent checkpoint relations"); + return; + } + prev =3D curr; + curr =3D curr->sibling; + } + if (prev) + prev->sibling =3D checkpoint->sibling; + else + checkpoint->parent->first_child =3D checkpoint->sibling; + checkpoint->parent =3D NULL; + checkpoint->sibling =3D NULL; +} + +int +virDomainListAllCheckpoints(virDomainCheckpointObjListPtr checkpoints, + virDomainCheckpointObjPtr from, + virDomainPtr dom, + virDomainCheckpointPtr **chks, + unsigned int flags) +{ + int count =3D virDomainCheckpointObjListNum(checkpoints, from, flags); + virDomainCheckpointPtr *list =3D NULL; + char **names; + int ret =3D -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 =3D 0; i < count; i++) + if ((list[i] =3D virGetDomainCheckpoint(dom, names[i])) =3D=3D NUL= L) + goto cleanup; + + ret =3D count; + *chks =3D list; + + cleanup: + for (i =3D 0; i < count; i++) + VIR_FREE(names[i]); + VIR_FREE(names); + if (ret < 0 && list) { + for (i =3D 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 =3D *defptr; + int ret =3D -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 =3D virDomainCheckpointFindByName(vm->checkpoints, def->pare= nt); + 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 =3D 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 =3D 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 =3D other->def->dom; + other->def->dom =3D NULL; + } + } + + if (def->dom) { + if (virDomainCheckpointAlignDisks(def) < 0) { + /* revert stealing of the checkpoint domain definition */ + if (def->dom && !other->def->dom) { + other->def->dom =3D def->dom; + def->dom =3D NULL; + } + goto cleanup; + } + } + + if (other =3D=3D vm->current_checkpoint) { + *update_current =3D true; + vm->current_checkpoint =3D NULL; + } + + /* Drop and rebuild the parent relationship, but keep all + * child relations by reusing chk. */ + virDomainCheckpointDropParent(other); + virDomainCheckpointDefFree(other->def); + other->def =3D def; + *defptr =3D NULL; + *chk =3D other; + } else if (def->dom && virDomainCheckpointAlignDisks(def) < 0) { + goto cleanup; + } + + ret =3D 0; + cleanup: + return ret; +} diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index 347eeeef7c..c13cacede3 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. * @@ -31,6 +31,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" @@ -3181,6 +3182,7 @@ static void virDomainObjDispose(void *obj) (dom->privateDataFreeFunc)(dom->privateData); virDomainSnapshotObjListFree(dom->snapshots); + virDomainCheckpointObjListFree(dom->checkpoints); } virDomainObjPtr @@ -3210,6 +3212,9 @@ virDomainObjNew(virDomainXMLOptionPtr xmlopt) if (!(domain->snapshots =3D virDomainSnapshotObjListNew())) goto error; + if (!(domain->checkpoints =3D 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 4d79ec7adb..4813069596 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 9ba6f50118..b1a3341ba6 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -286,7 +286,7 @@ endif WITH_LIBXL if WITH_QEMU test_programs +=3D qemuxml2argvtest qemuxml2xmltest \ - qemuargv2xmltest domainsnapshotxml2xmltest \ + qemuargv2xmltest domaincheckpointxml2xmltest domainsnapshotxml2xmltest \ qemumonitorjsontest qemuhotplugtest \ qemuagenttest qemucapabilitiestest qemucaps2xmltest \ qemumemlocktest \ @@ -668,6 +668,11 @@ qemublocktest_LDADD =3D \ $(LDADDS) \ $(NULL) +domaincheckpointxml2xmltest_SOURCES =3D \ + domaincheckpointxml2xmltest.c testutilsqemu.c testutilsqemu.h \ + testutils.c testutils.h +domaincheckpointxml2xmltest_LDADD =3D $(qemu_LDADDS) $(LDADDS) + domainsnapshotxml2xmltest_SOURCES =3D \ domainsnapshotxml2xmltest.c testutilsqemu.c testutilsqemu.h \ testutils.c testutils.h @@ -689,7 +694,7 @@ qemumigparamstest_LDADD =3D libqemumonitortestutils.la \ else ! WITH_QEMU EXTRA_DIST +=3D 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/domaincheckpointxm= l2xmltest.c new file mode 100644 index 0000000000..5381b6352b --- /dev/null +++ b/tests/domaincheckpointxml2xmltest.c @@ -0,0 +1,231 @@ +#include + +#include +#include +#include +#include + +#include +#include + +#include + +#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: + * 1234352345 if the checkpoint has no name, + * 23523452345. + */ +static const char *testCheckpointXMLVariableLineRegexStr =3D + "<(name|creationTime)>[0-9]+"; + +regex_t *testCheckpointXMLVariableLineRegex =3D NULL; + +static char * +testFilterXML(char *xml) +{ + virBuffer buf =3D VIR_BUFFER_INITIALIZER; + char **xmlLines =3D NULL; + char **xmlLine; + char *ret =3D NULL; + + if (!(xmlLines =3D virStringSplit(xml, "\n", 0))) { + VIR_FREE(xml); + goto cleanup; + } + VIR_FREE(xml); + + for (xmlLine =3D xmlLines; *xmlLine; xmlLine++) { + if (regexec(testCheckpointXMLVariableLineRegex, + *xmlLine, 0, NULL, 0) =3D=3D 0) + continue; + + virBufferStrcat(&buf, *xmlLine, "\n", NULL); + } + + if (virBufferCheckError(&buf) < 0) + goto cleanup; + + ret =3D virBufferContentAndReset(&buf); + + cleanup: + virBufferFreeAndReset(&buf); + virStringListFree(xmlLines); + return ret; +} + +static int +testCompareXMLToXMLFiles(const char *inxml, + const char *outxml, + bool internal, + bool redefine) +{ + char *inXmlData =3D NULL; + char *outXmlData =3D NULL; + char *actual =3D NULL; + int ret =3D -1; + virDomainCheckpointDefPtr def =3D NULL; + unsigned int flags =3D VIR_DOMAIN_CHECKPOINT_PARSE_DISKS; + + if (internal) + flags |=3D VIR_DOMAIN_CHECKPOINT_PARSE_INTERNAL; + + if (redefine) + flags |=3D VIR_DOMAIN_CHECKPOINT_PARSE_REDEFINE; + + if (virTestLoadFile(inxml, &inXmlData) < 0) + goto cleanup; + + if (virTestLoadFile(outxml, &outXmlData) < 0) + goto cleanup; + + if (!(def =3D 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 =3D 77; + goto cleanup; + const char *def_dom =3D "" + "" + " fedora" + " 93a5c045-6457-2c09-e56c-927cdf34e178" +/* arch=3D'x86_64' machine=3D'pc'*/ + " hvm" + ""; + int dom_flags =3D VIR_DOMAIN_DEF_PARSE_INACTIVE; + if (!(def->dom =3D virDomainDefParseString(def_dom, driver.caps, + driver.xmlopt, NULL, + dom_flags))) + goto cleanup; + } + + if (!(actual =3D virDomainCheckpointDefFormat(def, driver.caps, + driver.xmlopt, + VIR_DOMAIN_DEF_FORMAT_SECU= RE, + internal))) + goto cleanup; + + if (!redefine) { + if (!(actual =3D testFilterXML(actual))) + goto cleanup; + + if (!(outXmlData =3D testFilterXML(outXmlData))) + goto cleanup; + } + + if (STRNEQ(outXmlData, actual)) { + virTestDifferenceFull(stderr, outXmlData, outxml, actual, inxml); + goto cleanup; + } + + ret =3D 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 =3D data; + + return testCompareXMLToXMLFiles(info->inxml, info->outxml, + info->internal, info->redefine); +} + + +static int +mymain(void) +{ + int ret =3D 0; + + if (qemuTestDriverInit(&driver) < 0) + return EXIT_FAILURE; + + if (VIR_ALLOC(testCheckpointXMLVariableLineRegex) < 0) + goto cleanup; + + if (regcomp(testCheckpointXMLVariableLineRegex, + testCheckpointXMLVariableLineRegexStr, + REG_EXTENDED | REG_NOSUB) !=3D 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 =3D {abs_srcdir "/" inpath "/" name ".x= ml", \ + abs_srcdir "/" outpath "/" name ".xm= l", \ + internal, redefine}; \ + if (virTestRun("CHECKPOINT XML-2-XML " prefix " " name, \ + testCompareXMLToXMLHelper, &info) < 0) \ + ret =3D -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 qemudBuildCommandL= ine + * 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 =3D=3D 0 ? EXIT_SUCCESS : EXIT_FAILURE; +} + +VIR_TEST_MAIN(mymain) + +#else + +int +main(void) +{ + return EXIT_AM_SKIP; +} + +#endif /* WITH_QEMU */ --=20 2.17.2 -- libvir-list mailing list libvir-list@redhat.com https://www.redhat.com/mailman/listinfo/libvir-list