From nobody Mon Feb 9 00:53:54 2026 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 ARC-Seal: i=1; a=rsa-sha256; t=1560916152; cv=none; d=zoho.com; s=zohoarc; b=SPcCSLobOVOigenkpmPNVI4iSX2sXZUIlPTrBjcHUIzlZe6HRpd4hErtmihkq4Vy7hFw7iSaHNMLSOcJGYJa99eSIP+SlWsBIfNJLWwqPd32l2F4vPixVbslGGH/mBFeIxaL0AA/33s2mRONanIxTC3naeAD1VkOJqdos5q5uQQ= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zoho.com; s=zohoarc; t=1560916152; h=Content-Type:Content-Transfer-Encoding:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:To:ARC-Authentication-Results; bh=W7PDwxHxZd1TJq3B7s8F5bXH5BfpFe79U8awUqNY+EQ=; b=mczjPfPk14EJlraK0Bp7c5fRtS+bUpbMFPaRbWP5dZxnQUPkH0Pa0aoNOZG/Gmh8bRNX2aOJ0nAisK6C/y92XQZ5HQxFJd8B+0ZbKG2Fh/0fRZZMj6ITLBSgDZ8T/sFjQEQ/kCcP42WOJYRvf/7Pg1qUw05IZHzCKB43dMD8mlc= ARC-Authentication-Results: i=1; mx.zoho.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 header.from= (p=none dis=none) header.from= Return-Path: Received: from mx1.redhat.com (mx1.redhat.com [209.132.183.28]) by mx.zohomail.com with SMTPS id 156091615256191.14991503015221; Tue, 18 Jun 2019 20:49:12 -0700 (PDT) Received: from smtp.corp.redhat.com (int-mx07.intmail.prod.int.phx2.redhat.com [10.5.11.22]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id 90703307FBCB; Wed, 19 Jun 2019 03:48:39 +0000 (UTC) Received: from colo-mx.corp.redhat.com (colo-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.21]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 64CEA1001DF3; Wed, 19 Jun 2019 03:48:30 +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 44AF34EBC5; Wed, 19 Jun 2019 03:48:10 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx05.intmail.prod.int.phx2.redhat.com [10.5.11.15]) by lists01.pubmisc.prod.ext.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id x5J3m3AG017738 for ; Tue, 18 Jun 2019 23:48:03 -0400 Received: by smtp.corp.redhat.com (Postfix) id 3DADE7DFF3; Wed, 19 Jun 2019 03:48:03 +0000 (UTC) Received: from blue.redhat.com (ovpn-116-44.phx2.redhat.com [10.3.116.44]) by smtp.corp.redhat.com (Postfix) with ESMTP id DD199176BC for ; Wed, 19 Jun 2019 03:48:02 +0000 (UTC) From: Eric Blake To: libvir-list@redhat.com Date: Tue, 18 Jun 2019 22:47:45 -0500 Message-Id: <20190619034754.2708-5-eblake@redhat.com> In-Reply-To: <20190619034754.2708-1-eblake@redhat.com> References: <20190619034754.2708-1-eblake@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.15 X-loop: libvir-list@redhat.com Subject: [libvirt] [PATCH 04/13] 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: , Content-Transfer-Encoding: quoted-printable Sender: libvir-list-bounces@redhat.com Errors-To: libvir-list-bounces@redhat.com X-Scanned-By: MIMEDefang 2.84 on 10.5.11.22 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.41]); Wed, 19 Jun 2019 03:49:11 +0000 (UTC) Content-Type: text/plain; charset="utf-8" Add a new file checkpoint_conf.c that performs the translation to and from new XML describing a checkpoint. The code shares a common base class with snapshots, since a checkpoint similarly represents the domain state at a moment in time. Add some basic testing of round trip XML handling through the new code. Signed-off-by: Eric Blake --- src/conf/checkpoint_conf.h | 94 +++ src/conf/virconftypes.h | 3 + po/POTFILES | 1 + src/conf/Makefile.inc.am | 2 + src/conf/checkpoint_conf.c | 575 ++++++++++++++++++ src/libvirt_private.syms | 8 + tests/Makefile.am | 9 +- .../internal-active-invalid.xml | 53 ++ .../internal-inactive-invalid.xml | 53 ++ tests/domaincheckpointxml2xmltest.c | 223 +++++++ 10 files changed, 1019 insertions(+), 2 deletions(-) create mode 100644 src/conf/checkpoint_conf.h create mode 100644 src/conf/checkpoint_conf.c create mode 100644 tests/domaincheckpointxml2xmlout/internal-active-invali= d.xml create mode 100644 tests/domaincheckpointxml2xmlout/internal-inactive-inva= lid.xml 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..665423d6e4 --- /dev/null +++ b/src/conf/checkpoint_conf.h @@ -0,0 +1,94 @@ +/* + * checkpoint_conf.h: domain checkpoint XML processing + * (based on snapshot_conf.h) + * + * 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 + * . + */ + +#pragma once + +#include "internal.h" +#include "domain_conf.h" +#include "moment_conf.h" +#include "virobject.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 */ +struct _virDomainCheckpointDef { + virDomainMomentDef parent; + + /* Additional Public XML. */ + size_t ndisks; /* should not exceed dom->ndisks */ + virDomainCheckpointDiskDef *disks; +}; + + +typedef enum { + VIR_DOMAIN_CHECKPOINT_PARSE_REDEFINE =3D 1 << 0, + VIR_DOMAIN_CHECKPOINT_PARSE_INTERNAL =3D 1 << 1, +} virDomainCheckpointParseFlags; + +typedef enum { + VIR_DOMAIN_CHECKPOINT_FORMAT_SECURE =3D 1 << 0, + VIR_DOMAIN_CHECKPOINT_FORMAT_NO_DOMAIN =3D 1 << 1, + VIR_DOMAIN_CHECKPOINT_FORMAT_SIZE =3D 1 << 2, + VIR_DOMAIN_CHECKPOINT_FORMAT_INTERNAL =3D 1 << 3, + VIR_DOMAIN_CHECKPOINT_FORMAT_CURRENT =3D 1 << 4, +} virDomainCheckpointFormatFlags; + +unsigned int virDomainCheckpointFormatConvertXMLFlags(unsigned int flags); + +virDomainCheckpointDefPtr virDomainCheckpointDefParseString(const char *xm= lStr, + virCapsPtr cap= s, + virDomainXMLOp= tionPtr xmlopt, + bool *current, + unsigned int f= lags); +virDomainCheckpointDefPtr virDomainCheckpointDefParseNode(xmlDocPtr xml, + xmlNodePtr root, + virCapsPtr caps, + virDomainXMLOpti= onPtr xmlopt, + bool *current, + unsigned int fla= gs); +virDomainCheckpointDefPtr virDomainCheckpointDefNew(void); +char *virDomainCheckpointDefFormat(virDomainCheckpointDefPtr def, + virCapsPtr caps, + virDomainXMLOptionPtr xmlopt, + unsigned int flags); +int virDomainCheckpointAlignDisks(virDomainCheckpointDefPtr checkpoint); + +VIR_ENUM_DECL(virDomainCheckpoint); diff --git a/src/conf/virconftypes.h b/src/conf/virconftypes.h index b8f553f7fb..fe3f0af14f 100644 --- a/src/conf/virconftypes.h +++ b/src/conf/virconftypes.h @@ -102,6 +102,9 @@ typedef virDomainBlkiotune *virDomainBlkiotunePtr; typedef struct _virDomainBlockIoTuneInfo virDomainBlockIoTuneInfo; typedef virDomainBlockIoTuneInfo *virDomainBlockIoTuneInfoPtr; +typedef struct _virDomainCheckpointDef virDomainCheckpointDef; +typedef virDomainCheckpointDef *virDomainCheckpointDefPtr; + typedef struct _virDomainChrDef virDomainChrDef; typedef virDomainChrDef *virDomainChrDefPtr; 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 6b52ba674b..f0135bcb1c 100644 --- a/src/conf/Makefile.inc.am +++ b/src/conf/Makefile.inc.am @@ -14,6 +14,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..a7a34d3887 --- /dev/null +++ b/src/conf/checkpoint_conf.c @@ -0,0 +1,575 @@ +/* + * 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 + * . + */ + +#include + +#include "internal.h" +#include "virbitmap.h" +#include "virbuffer.h" +#include "datatypes.h" +#include "domain_conf.h" +#include "virlog.h" +#include "viralloc.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"); + +static virClassPtr virDomainCheckpointDefClass; +static void virDomainCheckpointDefDispose(void *obj); + +static int +virDomainCheckpointOnceInit(void) +{ + if (!VIR_CLASS_NEW(virDomainCheckpointDef, virClassForDomainMomentDef(= ))) + return -1; + + return 0; +} + +VIR_ONCE_GLOBAL_INIT(virDomainCheckpoint); + +VIR_ENUM_IMPL(virDomainCheckpoint, + VIR_DOMAIN_CHECKPOINT_TYPE_LAST, + "default", "no", "bitmap"); + + +/* Checkpoint Def functions */ +static void +virDomainCheckpointDiskDefClear(virDomainCheckpointDiskDefPtr disk) +{ + VIR_FREE(disk->name); + VIR_FREE(disk->bitmap); +} + +/* Allocate a new virDomainCheckpointDef; free with virObjectUnref() */ +virDomainCheckpointDefPtr +virDomainCheckpointDefNew(void) +{ + virDomainCheckpointDefPtr def; + + if (virDomainCheckpointInitialize() < 0) + return NULL; + + def =3D virObjectNew(virDomainCheckpointDefClass); + return def; +} + +static void +virDomainCheckpointDefDispose(void *obj) +{ + virDomainCheckpointDefPtr def =3D obj; + size_t i; + + for (i =3D 0; i < def->ndisks; i++) + virDomainCheckpointDiskDefClear(&def->disks[i]); + VIR_FREE(def->disks); +} + +static int +virDomainCheckpointDiskDefParseXML(xmlNodePtr node, + xmlXPathContextPtr ctxt, + 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; + } + + 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. If flags does not include + * VIR_DOMAIN_CHECKPOINT_PARSE_INTERNAL, then current is ignored. + */ +static virDomainCheckpointDefPtr +virDomainCheckpointDefParse(xmlXPathContextPtr ctxt, + virCapsPtr caps, + virDomainXMLOptionPtr xmlopt, + bool *current, + unsigned int flags) +{ + virDomainCheckpointDefPtr def =3D NULL; + virDomainCheckpointDefPtr ret =3D NULL; + xmlNodePtr *nodes =3D NULL; + size_t i; + int n; + char *creation =3D NULL; + int active; + char *tmp; + + if (!(def =3D virDomainCheckpointDefNew())) + return NULL; + + def->parent.name =3D virXPathString("string(./name)", ctxt); + if (def->parent.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; + } + } + + def->parent.description =3D virXPathString("string(./description)", ct= xt); + + if (flags & VIR_DOMAIN_CHECKPOINT_PARSE_REDEFINE) { + if (virXPathLongLong("string(./creationTime)", ctxt, + &def->parent.creationTime) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing creationTime from existing checkpoin= t")); + goto cleanup; + } + + def->parent.parent_name =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->parent.dom =3D virDomainDefParseNode(ctxt->node->doc, dom= ainNode, + caps, xmlopt, NULL, + domainflags); + if (!def->parent.dom) + goto cleanup; + } else { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing domain in checkpoint redefine")); + goto cleanup; + } + } else if (virDomainXMLOptionRunMomentPostParse(xmlopt, &def->parent) = < 0) { + goto cleanup; + } + + if ((n =3D virXPathNodeSet("./disks/*", ctxt, &nodes)) < 0) + goto cleanup; + 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, + &def->disks[i]) < 0) + goto cleanup; + } + + if (flags & VIR_DOMAIN_CHECKPOINT_PARSE_INTERNAL) { + if (!current) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("internal parse requested with NULL current")= ); + goto cleanup; + } + if (virXPathInt("string(./active)", ctxt, &active) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Could not find 'active' element")); + goto cleanup; + } + *current =3D active !=3D 0; + } + + VIR_STEAL_PTR(ret, def); + + cleanup: + VIR_FREE(creation); + VIR_FREE(nodes); + virObjectUnref(def); + + return ret; +} + +virDomainCheckpointDefPtr +virDomainCheckpointDefParseNode(xmlDocPtr xml, + xmlNodePtr root, + virCapsPtr caps, + virDomainXMLOptionPtr xmlopt, + bool *current, + 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, current, flags= ); + cleanup: + xmlXPathFreeContext(ctxt); + return def; +} + +virDomainCheckpointDefPtr +virDomainCheckpointDefParseString(const char *xmlStr, + virCapsPtr caps, + virDomainXMLOptionPtr xmlopt, + bool *current, + 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, current, 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->parent.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->parent.dom) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing domain in checkpoint")); + goto cleanup; + } + + if (def->ndisks > def->parent.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->parent.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->parent.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->parent.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->parent.dom->disks[idx]->dst)) { + VIR_FREE(disk->name); + if (VIR_STRDUP(disk->name, def->parent.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->parent.dom->ndisks - def->ndisks) < 0) + goto cleanup; + + for (i =3D 0; i < def->parent.dom->ndisks; i++) { + virDomainCheckpointDiskDefPtr disk; + + if (virBitmapIsBitSet(map, i)) + continue; + disk =3D &def->disks[ndisks++]; + if (VIR_STRDUP(disk->name, def->parent.dom->disks[i]->dst) < 0) + goto cleanup; + disk->idx =3D i; + + /* Don't checkpoint empty drives */ + if (virStorageSourceIsEmpty(def->parent.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; +} + + +/* Converts public VIR_DOMAIN_CHECKPOINT_XML_* into + * VIR_DOMAIN_CHECKPOINT_FORMAT_* flags, and silently ignores any other + * flags. */ +unsigned int virDomainCheckpointFormatConvertXMLFlags(unsigned int flags) +{ + unsigned int formatFlags =3D 0; + + if (flags & VIR_DOMAIN_CHECKPOINT_XML_SECURE) + formatFlags |=3D VIR_DOMAIN_CHECKPOINT_FORMAT_SECURE; + if (flags & VIR_DOMAIN_CHECKPOINT_XML_NO_DOMAIN) + formatFlags |=3D VIR_DOMAIN_CHECKPOINT_FORMAT_NO_DOMAIN; + if (flags & VIR_DOMAIN_CHECKPOINT_XML_SIZE) + formatFlags |=3D VIR_DOMAIN_CHECKPOINT_FORMAT_SIZE; + + return formatFlags; +} + + +static int +virDomainCheckpointDiskDefFormat(virBufferPtr buf, + virDomainCheckpointDiskDefPtr disk, + unsigned int flags) +{ + if (!disk->name) + return 0; + + virBufferEscapeString(buf, "name); + 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_FORMAT_SIZE) + virBufferAsprintf(buf, " size=3D'%llu'", disk->size); + } + virBufferAddLit(buf, "/>\n"); + return 0; +} + + +static int +virDomainCheckpointDefFormatInternal(virBufferPtr buf, + virDomainCheckpointDefPtr def, + virCapsPtr caps, + virDomainXMLOptionPtr xmlopt, + unsigned int flags) +{ + size_t i; + unsigned int domainflags =3D VIR_DOMAIN_DEF_FORMAT_INACTIVE; + + if (flags & VIR_DOMAIN_CHECKPOINT_FORMAT_SECURE) + domainflags |=3D VIR_DOMAIN_DEF_FORMAT_SECURE; + + virBufferAddLit(buf, "\n"); + virBufferAdjustIndent(buf, 2); + + virBufferEscapeString(buf, "%s\n", def->parent.name); + virBufferEscapeString(buf, "%s\n", + def->parent.description); + + if (def->parent.parent_name) { + virBufferAddLit(buf, "\n"); + virBufferAdjustIndent(buf, 2); + virBufferEscapeString(buf, "%s\n", + def->parent.parent_name); + virBufferAdjustIndent(buf, -2); + virBufferAddLit(buf, "\n"); + } + + if (def->parent.creationTime) + virBufferAsprintf(buf, "%lld\n", + def->parent.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) < 0) + goto error; + } + virBufferAdjustIndent(buf, -2); + virBufferAddLit(buf, "\n"); + } + + if (!(flags & VIR_DOMAIN_CHECKPOINT_FORMAT_NO_DOMAIN) && + virDomainDefFormatInternal(def->parent.dom, caps, domainflags, buf, + xmlopt) < 0) + goto error; + + if (flags & VIR_DOMAIN_CHECKPOINT_FORMAT_INTERNAL) + virBufferAsprintf(buf, "%d\n", + !!(flags & VIR_DOMAIN_CHECKPOINT_FORMAT_CURRENT)= ); + + virBufferAdjustIndent(buf, -2); + virBufferAddLit(buf, "\n"); + + if (virBufferCheckError(buf) < 0) + goto error; + + return 0; + + error: + virBufferFreeAndReset(buf); + return -1; +} + +char * +virDomainCheckpointDefFormat(virDomainCheckpointDefPtr def, + virCapsPtr caps, + virDomainXMLOptionPtr xmlopt, + unsigned int flags) +{ + virBuffer buf =3D VIR_BUFFER_INITIALIZER; + + virCheckFlags(VIR_DOMAIN_CHECKPOINT_FORMAT_SECURE | + VIR_DOMAIN_CHECKPOINT_FORMAT_NO_DOMAIN | + VIR_DOMAIN_CHECKPOINT_FORMAT_SIZE | + VIR_DOMAIN_CHECKPOINT_FORMAT_INTERNAL | + VIR_DOMAIN_CHECKPOINT_FORMAT_CURRENT, NULL); + if (virDomainCheckpointDefFormatInternal(&buf, def, caps, xmlopt, + flags) < 0) + return NULL; + + return virBufferContentAndReset(&buf); +} diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 0e6a39b59b..6c504dfc86 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -69,6 +69,14 @@ virCapabilitiesSetHostCPU; virCapabilitiesSetNetPrefix; +# conf/checkpoint_conf.h +virDomainCheckpointDefFormat; +virDomainCheckpointDefNew; +virDomainCheckpointDefParseString; +virDomainCheckpointTypeFromString; +virDomainCheckpointTypeToString; + + # conf/cpu_conf.h virCPUCacheModeTypeFromString; virCPUCacheModeTypeToString; diff --git a/tests/Makefile.am b/tests/Makefile.am index f9f5118aa1..65448e88a9 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -278,7 +278,7 @@ endif WITH_LIBXL if WITH_QEMU test_programs +=3D qemuxml2argvtest qemuxml2xmltest \ - qemuargv2xmltest domainsnapshotxml2xmltest \ + qemuargv2xmltest domaincheckpointxml2xmltest domainsnapshotxml2xmltest \ qemumonitorjsontest qemuhotplugtest \ qemuagenttest qemucapabilitiestest qemucaps2xmltest \ qemumemlocktest \ @@ -665,6 +665,11 @@ qemublocktest_LDADD =3D \ $(qemu_LDADDS) \ $(NULL) +domaincheckpointxml2xmltest_SOURCES =3D \ + domaincheckpointxml2xmltest.c testutilsqemu.c testutilsqemu.h \ + testutils.c testutils.h +domaincheckpointxml2xmltest_LDADD =3D $(qemu_LDADDS) + domainsnapshotxml2xmltest_SOURCES =3D \ domainsnapshotxml2xmltest.c testutilsqemu.c testutilsqemu.h \ testutils.c testutils.h @@ -700,7 +705,7 @@ qemufirmwaretest_LDADD =3D $(qemu_LDADDS) 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/domaincheckpointxml2xmlout/internal-active-invalid.xml b= /tests/domaincheckpointxml2xmlout/internal-active-invalid.xml new file mode 100644 index 0000000000..a518c58915 --- /dev/null +++ b/tests/domaincheckpointxml2xmlout/internal-active-invalid.xml @@ -0,0 +1,53 @@ + + 1525889631 + Completion of updates after OS install + + 1525111885 + + 1525889631 + + + + + + QEMUGuest1 + c7a5fdbd-edaf-9455-926a-d65c16db1809 + 219136 + 219136 + 1 + + hvm + + + + destroy + restart + destroy + + /usr/bin/qemu-system-i686 + + + + +
+ + + + + +
+ + +
+ + +
+ + + + + + + + 1 + diff --git a/tests/domaincheckpointxml2xmlout/internal-inactive-invalid.xml= b/tests/domaincheckpointxml2xmlout/internal-inactive-invalid.xml new file mode 100644 index 0000000000..df14c97836 --- /dev/null +++ b/tests/domaincheckpointxml2xmlout/internal-inactive-invalid.xml @@ -0,0 +1,53 @@ + + 1525889631 + Completion of updates after OS install + + 1525111885 + + 1525889631 + + + + + + QEMUGuest1 + c7a5fdbd-edaf-9455-926a-d65c16db1809 + 219136 + 219136 + 1 + + hvm + + + + destroy + restart + destroy + + /usr/bin/qemu-system-i686 + + + + +
+ + + + + +
+ + +
+ + +
+ + + + + + + + 0 + diff --git a/tests/domaincheckpointxml2xmltest.c b/tests/domaincheckpointxm= l2xmltest.c new file mode 100644 index 0000000000..59b3c12e8b --- /dev/null +++ b/tests/domaincheckpointxml2xmltest.c @@ -0,0 +1,223 @@ +#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; + +enum { + TEST_INTERNAL =3D 1 << 0, /* Test use of INTERNAL parse/format flag */ + TEST_REDEFINE =3D 1 << 1, /* Test use of REDEFINE parse flag */ + TEST_PARENT =3D 1 << 2, /* hard-code parent after parse */ + TEST_VDA_BITMAP =3D 1 << 3, /* hard-code disk vda after parse */ + TEST_SIZE =3D 1 << 4, /* Test use of SIZE format flag */ +}; + +static int +testCompareXMLToXMLFiles(const char *inxml, + const char *outxml, + unsigned int flags) +{ + char *inXmlData =3D NULL; + char *outXmlData =3D NULL; + char *actual =3D NULL; + int ret =3D -1; + virDomainCheckpointDefPtr def =3D NULL; + unsigned int parseflags =3D 0; + unsigned int formatflags =3D VIR_DOMAIN_CHECKPOINT_FORMAT_SECURE; + bool cur =3D false; + + if (flags & TEST_INTERNAL) { + parseflags |=3D VIR_DOMAIN_CHECKPOINT_PARSE_INTERNAL; + formatflags |=3D VIR_DOMAIN_CHECKPOINT_FORMAT_INTERNAL; + } + + if (flags & TEST_REDEFINE) + parseflags |=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, &cur, + parseflags))) + goto cleanup; + if (cur) { + if (!(flags & TEST_REDEFINE)) + goto cleanup; + formatflags |=3D VIR_DOMAIN_CHECKPOINT_FORMAT_CURRENT; + } + if (flags & TEST_PARENT) { + if (def->parent.parent_name) + goto cleanup; + if (VIR_STRDUP(def->parent.parent_name, "1525111885") < 0) + goto cleanup; + } + if (flags & TEST_VDA_BITMAP) { + virDomainCheckpointDiskDefPtr disk; + + if (VIR_EXPAND_N(def->disks, def->ndisks, 1) < 0) + goto cleanup; + disk =3D &def->disks[0]; + if (disk->bitmap) + goto cleanup; + if (!disk->name) { + disk->type =3D VIR_DOMAIN_CHECKPOINT_TYPE_BITMAP; + if (VIR_STRDUP(disk->name, "vda") < 0) + goto cleanup; + } else if (STRNEQ(disk->name, "vda")) { + goto cleanup; + } + if (VIR_STRDUP(disk->bitmap, def->parent.name) < 0) + goto cleanup; + } + if (flags & TEST_SIZE) { + def->disks[0].size =3D 1048576; + formatflags |=3D VIR_DOMAIN_CHECKPOINT_FORMAT_SIZE; + } + + /* Parsing XML does not populate the domain definition; work + * around that by not requesting domain on output */ + if (!def->parent.dom) + formatflags |=3D VIR_DOMAIN_CHECKPOINT_FORMAT_NO_DOMAIN; + + if (!(actual =3D virDomainCheckpointDefFormat(def, driver.caps, + driver.xmlopt, + formatflags))) + 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); + virObjectUnref(def); + return ret; +} + +struct testInfo { + const char *inxml; + const char *outxml; + long long creationTime; + unsigned int flags; +}; +static long long mocktime; + +static int +testCheckpointPostParse(virDomainMomentDefPtr def) +{ + if (!mocktime) + return 0; + if (def->creationTime) + return -1; + def->creationTime =3D mocktime; + if (!def->name && + virAsprintf(&def->name, "%lld", def->creationTime) < 0) + return -1; + return 0; +} + +static int +testCompareXMLToXMLHelper(const void *data) +{ + const struct testInfo *info =3D data; + + mocktime =3D info->creationTime; + return testCompareXMLToXMLFiles(info->inxml, info->outxml, info->flags= ); +} + + +static int +mymain(void) +{ + int ret =3D 0; + + if (qemuTestDriverInit(&driver) < 0) + return EXIT_FAILURE; + + virDomainXMLOptionSetMomentPostParse(driver.xmlopt, + testCheckpointPostParse); + +# define DO_TEST(prefix, name, inpath, outpath, time, flags) \ + do { \ + const struct testInfo info =3D {abs_srcdir "/" inpath "/" name ".x= ml", \ + abs_srcdir "/" outpath "/" name ".xm= l", \ + time, flags}; \ + if (virTestRun("CHECKPOINT XML-2-XML " prefix " " name, \ + testCompareXMLToXMLHelper, &info) < 0) \ + ret =3D -1; \ + } while (0) + +# define DO_TEST_INOUT(name, time, flags) \ + DO_TEST("in->out", name, \ + "domaincheckpointxml2xmlin", \ + "domaincheckpointxml2xmlout", \ + time, flags) +# define DO_TEST_OUT(name, flags) \ + DO_TEST("out->out", name, \ + "domaincheckpointxml2xmlout", \ + "domaincheckpointxml2xmlout", \ + 0, flags | TEST_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); + + /* Tests of internal state saving - the element is not + * permitted or exposed to user XML, so the files are named to + * skip schema validation */ + DO_TEST_OUT("internal-active-invalid", TEST_INTERNAL); + DO_TEST_OUT("internal-inactive-invalid", TEST_INTERNAL); + /* Test a normal user redefine */ + DO_TEST_OUT("redefine", 0); + + /* Tests of valid user input, and resulting output */ + DO_TEST_INOUT("empty", 1525889631, TEST_VDA_BITMAP); + DO_TEST_INOUT("sample", 1525889631, TEST_PARENT | TEST_VDA_BITMAP); + DO_TEST_INOUT("size", 1553648510, + TEST_PARENT | TEST_VDA_BITMAP | TEST_SIZE); + + 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.20.1 -- libvir-list mailing list libvir-list@redhat.com https://www.redhat.com/mailman/listinfo/libvir-list