From d6f6b2d194c5771600e08400e8849e2b96f9ecc9 Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Thu, 18 Aug 2011 17:33:36 -0600 Subject: [PATCH] snapshot: add to snapshot xml Adds an optional element to , which will be used to give user control over external snapshot filenames on input, and specify generated filenames on output. For now, no driver accepts this element; that will come later. ... ... * src/conf/domain_conf.h (_virDomainSnapshotDiskDef): New type. (_virDomainSnapshotDef): Add new elements. (virDomainSnapshotAlignDisks): New prototype. * src/conf/domain_conf.c (virDomainSnapshotDiskDefClear) (virDomainSnapshotDiskDefParseXML, disksorter) (virDomainSnapshotAlignDisks): New functions. (virDomainSnapshotDefParseString): Parse new fields. (virDomainSnapshotDefFree): Clean them up. (virDomainSnapshotDefFormat): Output them. * src/libvirt_private.syms (domain_conf.h): Export new function. * docs/schemas/domainsnapshot.rng (domainsnapshot, disksnapshot): Add more xml. * docs/formatsnapshot.html.in: Document it. * tests/domainsnapshotxml2xmlin/disk_snapshot.xml: New test. * tests/domainsnapshotxml2xmlout/disk_snapshot.xml: Update. --- docs/formatsnapshot.html.in | 117 +++++++- docs/schemas/domainsnapshot.rng | 52 ++++ src/conf/domain_conf.c | 280 ++++++++++++++++++ src/conf/domain_conf.h | 25 +- src/libvirt_private.syms | 1 + .../domainsnapshotxml2xmlin/disk_snapshot.xml | 16 + .../disk_snapshot.xml | 42 +++ 7 files changed, 526 insertions(+), 7 deletions(-) create mode 100644 tests/domainsnapshotxml2xmlin/disk_snapshot.xml diff --git a/docs/formatsnapshot.html.in b/docs/formatsnapshot.html.in index a0940ac4ba..5c6b28202e 100644 --- a/docs/formatsnapshot.html.in +++ b/docs/formatsnapshot.html.in @@ -68,9 +68,9 @@

Attributes of libvirt snapshots are stored as child elements of the domainsnapshot element. At snapshot creation - time, normally only the name - and description elements are settable; the rest of - the fields are ignored on creation, and will be filled in by + time, normally only the name, description, + and disks elements are settable; the rest of the + fields are ignored on creation, and will be filled in by libvirt in for informational purposes by virDomainSnapshotGetXMLDesc(). However, when redefining a snapshot (since 0.9.5), @@ -106,6 +106,58 @@ description is omitted when initially creating the snapshot, then this field will be empty. +

disks
+
On input, this is an optional listing of specific + instructions for disk snapshots; it is needed when making a + snapshot of only a subset of the disks associated with a + domain, or when overriding the domain defaults for how to + snapshot each disk, or for providing specific control over + what file name is created in an external snapshot. On output, + this is fully populated to show the state of each disk in the + snapshot, including any properties that were generated by the + hypervisor defaults. For system checkpoints, this field is + ignored on input and omitted on output (a system checkpoint + implies that all disks participate in the snapshot process, + and since the current implementation only does internal system + checkpoints, there are no extra details to add); a future + release may allow the use of disks with a system + checkpoint. This element has a list of disk + sub-elements, describing anywhere from zero to all of the + disks associated with the domain. Since + 0.9.5 +
+
disk
+
This sub-element describes the snapshot properties of a + specific disk. The attribute name is + mandatory, and must match the <target + dev='name'/> of one of + the disk + devices specified for the domain at the time of the + snapshot. The attribute snapshot is + optional, and has the same values of the disk device + element for a domain + (no, internal, + or external). Some hypervisors like ESX + require that if specified, the snapshot mode must not + override any snapshot mode attached to the corresponding + domain disk, while others like qemu allow this field to + override the domain default. If the snapshot mode is + external (whether specified or inherited), then there is + an optional sub-element source, with an + attribute file giving the name, and an + optional sub-element driver, with an + attribute type giving the driver type (such + as qcow2), of the new file created by the external + snapshot of the new file. If source is not + given, a file name is generated that consists of the + existing file name with anything after the trailing dot + replaced by the snapshot name. Remember that with external + snapshots, the original file name becomes the read-only + snapshot, and the new file name contains the read-write + delta of all disk changes since the snapshot. +
+
+
creationTime
The time this snapshot was created. The time is specified in seconds since the Epoch, UTC (i.e. Unix time). Readonly. @@ -145,14 +197,21 @@

Examples

-

Using this XML on creation:

+

Using this XML to create a disk snapshot of just vda on a qemu + domain with two disks:

 <domainsnapshot>
   <description>Snapshot of OS install and updates</description>
+  <disks>
+    <disk name='vda'>
+      <source file='/path/to/new'/>
+    </disk>
+    <disk name='vdb' snapshot='no'/>
+  </disks>
 </domainsnapshot>

will result in XML similar to this from - virDomainSnapshotGetXMLDesc:

+ virDomainSnapshotGetXMLDesc():

 <domainsnapshot>
   <name>1270477159</name>
@@ -162,13 +221,61 @@
   <parent>
     <name>bare-os-install</name>
   </parent>
+  <disks>
+    <disk name='vda' snapshot='external'>
+      <driver type='qcow2'/>
+      <source file='/path/to/new'/>
+    </disk>
+    <disk name='vdb' snapshot='no'/>
+  </disks>
   <domain>
     <name>fedora</name>
     <uuid>93a5c045-6457-2c09-e56c-927cdf34e178</uuid>
     <memory>1048576</memory>
     ...
+    <devices>
+      <disk type='file' device='disk'>
+        <driver name='qemu' type='raw'/>
+        <source file='/path/to/old'/>
+        <target dev='vda' bus='virtio'/>
+      </disk>
+      <disk type='file' device='disk' snapshot='external'>
+        <driver name='qemu' type='raw'/>
+        <source file='/path/to/old2'/>
+        <target dev='vdb' bus='virtio'/>
+      </disk>
+      ...
     </devices>
   </domain>
 </domainsnapshot>
+ +

With that snapshot created, /path/to/old is the + read-only backing file to the new active + file /path/to/new. The <domain> + element within the snapshot xml records the state of the domain + just before the snapshot; a call + to virDomainGetXMLDesc() will show that the domain + has been changed to reflect the snapshot: +

+
+<domain>
+  <name>fedora</name>
+  <uuid>93a5c045-6457-2c09-e56c-927cdf34e178</uuid>
+  <memory>1048576</memory>
+  ...
+  <devices>
+    <disk type='file' device='disk'>
+      <driver name='qemu' type='qcow2'/>
+      <source file='/path/to/new'/>
+      <target dev='vda' bus='virtio'/>
+    </disk>
+    <disk type='file' device='disk' snapshot='external'>
+      <driver name='qemu' type='raw'/>
+      <source file='/path/to/old2'/>
+      <target dev='vdb' bus='virtio'/>
+    </disk>
+    ...
+  </devices>
+</domain>
diff --git a/docs/schemas/domainsnapshot.rng b/docs/schemas/domainsnapshot.rng index 130dad9ca6..671fbe0698 100644 --- a/docs/schemas/domainsnapshot.rng +++ b/docs/schemas/domainsnapshot.rng @@ -30,6 +30,13 @@ + + + + + + + @@ -72,4 +79,49 @@ + + + + + + + + no + + + internal + + + + + external + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index 270d02dcd2..1881d3f8c1 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -11411,18 +11411,82 @@ cleanup: } /* Snapshot Def functions */ +static void +virDomainSnapshotDiskDefClear(virDomainSnapshotDiskDefPtr disk) +{ + VIR_FREE(disk->name); + VIR_FREE(disk->file); + VIR_FREE(disk->driverType); +} + void virDomainSnapshotDefFree(virDomainSnapshotDefPtr def) { + int i; + if (!def) return; VIR_FREE(def->name); VIR_FREE(def->description); VIR_FREE(def->parent); + for (i = 0; i < def->ndisks; i++) + virDomainSnapshotDiskDefClear(&def->disks[i]); + VIR_FREE(def->disks); virDomainDefFree(def->dom); VIR_FREE(def); } +static int +virDomainSnapshotDiskDefParseXML(xmlNodePtr node, + virDomainSnapshotDiskDefPtr def) +{ + int ret = -1; + char *snapshot = NULL; + xmlNodePtr cur; + + def->name = virXMLPropString(node, "name"); + if (!def->name) { + virDomainReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing name from disk snapshot element")); + goto cleanup; + } + + snapshot = virXMLPropString(node, "snapshot"); + if (snapshot) { + def->snapshot = virDomainDiskSnapshotTypeFromString(snapshot); + if (def->snapshot <= 0) { + virDomainReportError(VIR_ERR_INTERNAL_ERROR, + _("unknown disk snapshot setting '%s'"), + snapshot); + goto cleanup; + } + } + + cur = node->children; + while (cur) { + if (cur->type == XML_ELEMENT_NODE) { + if (!def->file && + xmlStrEqual(cur->name, BAD_CAST "source")) { + def->file = virXMLPropString(cur, "file"); + } else if (!def->driverType && + xmlStrEqual(cur->name, BAD_CAST "driver")) { + def->driverType = virXMLPropString(cur, "type"); + } + } + cur = cur->next; + } + + if (!def->snapshot && (def->file || def->driverType)) + def->snapshot = VIR_DOMAIN_DISK_SNAPSHOT_EXTERNAL; + + ret = 0; +cleanup: + VIR_FREE(snapshot); + if (ret < 0) + virDomainSnapshotDiskDefClear(def); + return ret; +} + /* flags is bitwise-or of virDomainSnapshotParseFlags. * If flags does not include VIR_DOMAIN_SNAPSHOT_PARSE_REDEFINE, then * caps and expectedVirtTypes are ignored. @@ -11437,6 +11501,8 @@ virDomainSnapshotDefParseString(const char *xmlStr, xmlDocPtr xml = NULL; virDomainSnapshotDefPtr def = NULL; virDomainSnapshotDefPtr ret = NULL; + xmlNodePtr *nodes = NULL; + int i; char *creation = NULL, *state = NULL; struct timeval tv; int active; @@ -11478,6 +11544,25 @@ virDomainSnapshotDefParseString(const char *xmlStr, def->description = virXPathString("string(./description)", ctxt); + if ((i = virXPathNodeSet("./disks/*", ctxt, &nodes)) < 0) + goto cleanup; + if (flags & VIR_DOMAIN_SNAPSHOT_PARSE_DISKS) { + def->ndisks = i; + if (def->ndisks && VIR_ALLOC_N(def->disks, def->ndisks) < 0) { + virReportOOMError(); + goto cleanup; + } + for (i = 0; i < def->ndisks; i++) { + if (virDomainSnapshotDiskDefParseXML(nodes[i], &def->disks[i]) < 0) + goto cleanup; + } + VIR_FREE(nodes); + } else { + virDomainReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", + _("unable to handle disk requests in snapshot")); + goto cleanup; + } + if (flags & VIR_DOMAIN_SNAPSHOT_PARSE_REDEFINE) { if (virXPathLongLong("string(./creationTime)", ctxt, &def->creationTime) < 0) { @@ -11545,6 +11630,7 @@ virDomainSnapshotDefParseString(const char *xmlStr, cleanup: VIR_FREE(creation); VIR_FREE(state); + VIR_FREE(nodes); xmlXPathFreeContext(ctxt); if (ret == NULL) virDomainSnapshotDefFree(def); @@ -11553,12 +11639,178 @@ cleanup: return ret; } +static int +disksorter(const void *a, const void *b) +{ + const virDomainSnapshotDiskDef *diska = a; + const virDomainSnapshotDiskDef *diskb = b; + + /* Integer overflow shouldn't be a problem here. */ + return diska->index - diskb->index; +} + +/* Align def->disks to def->domain. Sort the list of def->disks, + * filling in any missing disks or snapshot state defaults given by + * the domain, with a fallback to a passed in default. Issue an error + * and return -1 if any def->disks[n]->name appears more than once or + * does not map to dom->disks. If require_match, also require that + * existing def->disks snapshot states do not override explicit + * def->dom settings. */ +int +virDomainSnapshotAlignDisks(virDomainSnapshotDefPtr def, + int default_snapshot, + bool require_match) +{ + int ret = -1; + virBitmapPtr map = NULL; + int i; + int ndisks; + bool inuse; + + if (!def->dom) { + virDomainReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing domain in snapshot")); + goto cleanup; + } + + if (def->ndisks > def->dom->ndisks) { + virDomainReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("too many disk snapshot requests for domain")); + goto cleanup; + } + + /* Unlikely to have a guest without disks but technically possible. */ + if (!def->dom->ndisks) { + ret = 0; + goto cleanup; + } + + if (!(map = virBitmapAlloc(def->dom->ndisks))) { + virReportOOMError(); + goto cleanup; + } + + /* Double check requested disks. */ + for (i = 0; i < def->ndisks; i++) { + virDomainSnapshotDiskDefPtr disk = &def->disks[i]; + int idx = virDomainDiskIndexByName(def->dom, disk->name); + int disk_snapshot; + + if (idx < 0) { + virDomainReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("no disk named '%s'"), disk->name); + goto cleanup; + } + disk_snapshot = def->dom->disks[idx]->snapshot; + + if (virBitmapGetBit(map, idx, &inuse) < 0 || inuse) { + virDomainReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("disk '%s' specified twice"), + disk->name); + goto cleanup; + } + ignore_value(virBitmapSetBit(map, idx)); + disk->index = idx; + if (!disk_snapshot) + disk_snapshot = default_snapshot; + if (!disk->snapshot) { + disk->snapshot = disk_snapshot; + } else if (disk_snapshot && require_match && + disk->snapshot != disk_snapshot) { + const char *tmp = virDomainDiskSnapshotTypeToString(disk_snapshot); + virDomainReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("disk '%s' must use snapshot mode '%s'"), + disk->name, tmp); + goto cleanup; + } + if (disk->file && + disk->snapshot != VIR_DOMAIN_DISK_SNAPSHOT_EXTERNAL) { + virDomainReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("file '%s' for disk '%s' requires " + "use of external snapshot mode"), + disk->file, disk->name); + 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) { + virReportOOMError(); + goto cleanup; + } + + for (i = 0; i < def->dom->ndisks; i++) { + virDomainSnapshotDiskDefPtr disk; + + ignore_value(virBitmapGetBit(map, i, &inuse)); + if (inuse) + continue; + disk = &def->disks[ndisks++]; + if (!(disk->name = strdup(def->dom->disks[i]->dst))) { + virReportOOMError(); + goto cleanup; + } + disk->index = i; + disk->snapshot = def->dom->disks[i]->snapshot; + if (!disk->snapshot) + disk->snapshot = default_snapshot; + } + + qsort(&def->disks[0], def->ndisks, sizeof(def->disks[0]), disksorter); + + /* Generate any default external file names. */ + for (i = 0; i < def->ndisks; i++) { + virDomainSnapshotDiskDefPtr disk = &def->disks[i]; + + if (disk->snapshot == VIR_DOMAIN_DISK_SNAPSHOT_EXTERNAL && + !disk->file) { + const char *original = def->dom->disks[i]->src; + const char *tmp; + + if (!original) { + virDomainReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("cannot generate external backup name " + "for disk '%s' without source"), + disk->name); + goto cleanup; + } + tmp = strrchr(original, '.'); + if (!tmp || strchr(tmp, '/')) { + ignore_value(virAsprintf(&disk->file, "%s.%s", + original, def->name)); + } else { + if ((tmp - original) > INT_MAX) { + virDomainReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("integer overflow")); + goto cleanup; + } + ignore_value(virAsprintf(&disk->file, "%.*s.%s", + (int) (tmp - original), original, + def->name)); + } + if (!disk->file) { + virReportOOMError(); + goto cleanup; + } + } + } + + ret = 0; + +cleanup: + virBitmapFree(map); + return ret; +} + char *virDomainSnapshotDefFormat(char *domain_uuid, virDomainSnapshotDefPtr def, unsigned int flags, int internal) { virBuffer buf = VIR_BUFFER_INITIALIZER; + int i; virCheckFlags(VIR_DOMAIN_XML_SECURE, NULL); @@ -11578,6 +11830,34 @@ char *virDomainSnapshotDefFormat(char *domain_uuid, } virBufferAsprintf(&buf, " %lld\n", def->creationTime); + /* For now, only output on disk-snapshot */ + if (def->state == VIR_DOMAIN_DISK_SNAPSHOT) { + virBufferAddLit(&buf, " \n"); + for (i = 0; i < def->ndisks; i++) { + virDomainSnapshotDiskDefPtr disk = &def->disks[i]; + + if (!disk->name) + continue; + + virBufferEscapeString(&buf, " name); + if (disk->snapshot) + virBufferAsprintf(&buf, " snapshot='%s'", + virDomainDiskSnapshotTypeToString(disk->snapshot)); + if (disk->file || disk->driverType) { + virBufferAddLit(&buf, ">\n"); + if (disk->file) + virBufferEscapeString(&buf, " \n", + disk->file); + if (disk->driverType) + virBufferEscapeString(&buf, " \n", + disk->driverType); + virBufferAddLit(&buf, " \n"); + } else { + virBufferAddLit(&buf, "/>\n"); + } + } + virBufferAddLit(&buf, " \n"); + } if (def->dom) { virDomainDefFormatInternal(def->dom, flags, &buf); } else { diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index 749fcce86d..fd4dbced62 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -1391,7 +1391,20 @@ enum virDomainTaintFlags { VIR_DOMAIN_TAINT_LAST }; -/* Snapshot state */ +/* Items related to snapshot state */ + +/* Stores disk-snapshot information */ +typedef struct _virDomainSnapshotDiskDef virDomainSnapshotDiskDef; +typedef virDomainSnapshotDiskDef *virDomainSnapshotDiskDefPtr; +struct _virDomainSnapshotDiskDef { + char *name; /* name matching the dom->disks that matches name */ + int snapshot; /* enum virDomainDiskSnapshot */ + char *file; /* new source file when snapshot is external */ + char *driverType; /* file format type of new file */ +}; + +/* Stores the complete snapshot metadata */ typedef struct _virDomainSnapshotDef virDomainSnapshotDef; typedef virDomainSnapshotDef *virDomainSnapshotDefPtr; struct _virDomainSnapshotDef { @@ -1401,6 +1414,10 @@ struct _virDomainSnapshotDef { char *parent; long long creationTime; /* in seconds */ int state; /* enum virDomainSnapshotState */ + + size_t ndisks; /* should not exceed dom->ndisks */ + virDomainSnapshotDiskDef *disks; + virDomainDefPtr dom; /* Internal use. */ @@ -1426,7 +1443,8 @@ struct _virDomainSnapshotObjList { typedef enum { VIR_DOMAIN_SNAPSHOT_PARSE_REDEFINE = 1 << 0, - VIR_DOMAIN_SNAPSHOT_PARSE_INTERNAL = 1 << 1, + VIR_DOMAIN_SNAPSHOT_PARSE_DISKS = 1 << 1, + VIR_DOMAIN_SNAPSHOT_PARSE_INTERNAL = 1 << 2, } virDomainSnapshotParseFlags; virDomainSnapshotDefPtr virDomainSnapshotDefParseString(const char *xmlStr, @@ -1438,6 +1456,9 @@ char *virDomainSnapshotDefFormat(char *domain_uuid, virDomainSnapshotDefPtr def, unsigned int flags, int internal); +int virDomainSnapshotAlignDisks(virDomainSnapshotDefPtr snapshot, + int default_snapshot, + bool require_match); virDomainSnapshotObjPtr virDomainSnapshotAssignDef(virDomainSnapshotObjListPtr snapshots, const virDomainSnapshotDefPtr def); diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 2341110ce9..8c100e7237 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -388,6 +388,7 @@ virDomainSmartcardDefForeach; virDomainSmartcardDefFree; virDomainSmartcardTypeFromString; virDomainSmartcardTypeToString; +virDomainSnapshotAlignDisks; virDomainSnapshotAssignDef; virDomainSnapshotDefFormat; virDomainSnapshotDefFree; diff --git a/tests/domainsnapshotxml2xmlin/disk_snapshot.xml b/tests/domainsnapshotxml2xmlin/disk_snapshot.xml new file mode 100644 index 0000000000..1f0beb6b06 --- /dev/null +++ b/tests/domainsnapshotxml2xmlin/disk_snapshot.xml @@ -0,0 +1,16 @@ + + my snap name + !@#$%^ + + + + + + + + + + + + + diff --git a/tests/domainsnapshotxml2xmlout/disk_snapshot.xml b/tests/domainsnapshotxml2xmlout/disk_snapshot.xml index 391bb5762d..e0414a1b9c 100644 --- a/tests/domainsnapshotxml2xmlout/disk_snapshot.xml +++ b/tests/domainsnapshotxml2xmlout/disk_snapshot.xml @@ -6,6 +6,23 @@ disk-snapshot 1272917631 + + + + + + + + + + + + + + + + + QEMUGuest1 c7a5fdbd-edaf-9455-926a-d65c16db1809 @@ -27,6 +44,31 @@
+ + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+