[libvirt] [PATCH] Compressed save image format for Qemu.

Implement a compressed save image format for qemu. While ideally we would have the choice between compressed/non-compressed available to the libvirt API, unfortunately there is no "flags" parameter to the virDomainSave() API. Therefore, implement this as a qemu.conf option. Both gzip and bzip2 are implemented, and it should be very easy to implement additional compression methods. One open question is if/how we should detect the gzip and bzip2 binaries. One way to do it is to do compile-time setting of the paths (via configure.in), but that doesn't seem like a great thing to do. Another solution (my preferred solution) is not to detect at all; when we go to run the commands that need them, if they aren't available, or aren't available in one of the standard paths, then we'll fail. Maybe somebody else has another option or opinion, though. In the future, we'll have a more robust (managed) save/restore API, at which time we can expose this functionality properly in the API. Signed-off-by: Chris Lalancette <clalance@redhat.com> --- src/qemu.conf | 10 +++++++ src/qemu_conf.c | 11 ++++++++ src/qemu_conf.h | 2 + src/qemu_driver.c | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 92 insertions(+), 4 deletions(-) diff --git a/src/qemu.conf b/src/qemu.conf index 653f487..86dcc9d 100644 --- a/src/qemu.conf +++ b/src/qemu.conf @@ -129,3 +129,13 @@ # "/dev/ptmx", "/dev/kvm", "/dev/kqemu", # "/dev/rtc", "/dev/hpet", "/dev/net/tun", #] + +# The default format for Qemu/KVM guest save images is raw; that is, the +# memory from the domain is dumped out directly to a file. If you have +# guests with a large amount of memory, however, this can take up quite +# a bit of space. If you would like to compress the images while they +# are being saved to disk, you can also set "gzip" or "bzip2" for the +# save_image_format. Note that this means you slow down the process +# of saving a domain in order to save disk space. +# +# save_image_format = "raw" diff --git a/src/qemu_conf.c b/src/qemu_conf.c index 7ca5a15..ed87e13 100644 --- a/src/qemu_conf.c +++ b/src/qemu_conf.c @@ -280,6 +280,17 @@ int qemudLoadDriverConfig(struct qemud_driver *driver, driver->cgroupDeviceACL[i] = NULL; } + p = virConfGetValue (conf, "save_image_format"); + CHECK_TYPE ("save_image_format", VIR_CONF_STRING); + if (p && p->str) { + VIR_FREE(driver->saveImageFormat); + if (!(driver->saveImageFormat = strdup(p->str))) { + virReportOOMError(NULL); + virConfFree(conf); + return -1; + } + } + virConfFree (conf); return 0; } diff --git a/src/qemu_conf.h b/src/qemu_conf.h index 8f4ef6a..e34baab 100644 --- a/src/qemu_conf.h +++ b/src/qemu_conf.h @@ -111,6 +111,8 @@ struct qemud_driver { char *securityDriverName; virSecurityDriverPtr securityDriver; + + char *saveImageFormat; }; diff --git a/src/qemu_driver.c b/src/qemu_driver.c index 3c92635..f642fea 100644 --- a/src/qemu_driver.c +++ b/src/qemu_driver.c @@ -3437,11 +3437,26 @@ static int qemudDomainSave(virDomainPtr dom, struct qemud_save_header header; int ret = -1; virDomainEventPtr event = NULL; + int internalret; memset(&header, 0, sizeof(header)); memcpy(header.magic, QEMUD_SAVE_MAGIC, sizeof(header.magic)); header.version = QEMUD_SAVE_VERSION; + if (driver->saveImageFormat == NULL) + header.compressed = QEMUD_SAVE_FORMAT_RAW; + else if (STREQ(driver->saveImageFormat, "raw")) + header.compressed = QEMUD_SAVE_FORMAT_RAW; + else if (STREQ(driver->saveImageFormat, "gzip")) + header.compressed = QEMUD_SAVE_FORMAT_GZIP; + else if (STREQ(driver->saveImageFormat, "bzip2")) + header.compressed = QEMUD_SAVE_FORMAT_BZIP2; + else { + qemudReportError(dom->conn, dom, NULL, VIR_ERR_OPERATION_FAILED, + "%s", _("Invalid save image format specified in configuration file")); + return -1; + } + qemuDriverLock(driver); vm = virDomainFindByUUID(&driver->domains, dom->uuid); @@ -3514,11 +3529,27 @@ static int qemudDomainSave(virDomainPtr dom, virReportOOMError(dom->conn); goto cleanup; } - if (virAsprintf(&command, "migrate \"exec:" - "dd of='%s' oflag=append conv=notrunc 2>/dev/null" - "\"", safe_path) == -1) { + + if (header.compressed == QEMUD_SAVE_FORMAT_RAW) + internalret = virAsprintf(&command, "migrate \"exec:" + "dd of='%s' oflag=append conv=notrunc 2>/dev/null" + "\"", safe_path); + else if (header.compressed == QEMUD_SAVE_FORMAT_GZIP) + internalret = virAsprintf(&command, "migrate \"exec:" + "gzip -c | dd of='%s' oflag=append conv=notrunc 2>/dev/null" + "\"", safe_path); + else if (header.compressed == QEMUD_SAVE_FORMAT_BZIP2) + internalret = virAsprintf(&command, "migrate \"exec:" + "bzip2 -c | dd of='%s' oflag=append conv=notrunc 2>/dev/null" + "\"", safe_path); + else { + qemudReportError(dom->conn, dom, NULL, VIR_ERR_INTERNAL_ERROR, + _("Invalid compress format %d"), + header.compressed); + goto cleanup; + } + if (internalret < 0) { virReportOOMError(dom->conn); - command = NULL; goto cleanup; } @@ -4039,6 +4070,9 @@ static int qemudDomainRestore(virConnectPtr conn, char *xml = NULL; struct qemud_save_header header; virDomainEventPtr event = NULL; + int intermediatefd = -1; + pid_t intermediate_pid = -1; + int childstat; qemuDriverLock(driver); /* Verify the header and read the XML */ @@ -4128,8 +4162,39 @@ static int qemudDomainRestore(virConnectPtr conn, } def = NULL; + if (header.version == 2) { + const char *intermediate_argv[3] = { NULL, "-dc", NULL }; + if (header.compressed == QEMUD_SAVE_FORMAT_GZIP) + intermediate_argv[0] = "gzip"; + else if (header.compressed == QEMUD_SAVE_FORMAT_BZIP2) + intermediate_argv[0] = "bzip2"; + else if (header.compressed != QEMUD_SAVE_FORMAT_RAW) { + qemudReportError(conn, NULL, NULL, VIR_ERR_OPERATION_FAILED, + _("Unknown compressed save format %d"), + header.compressed); + goto cleanup; + } + if (intermediate_argv[0] != NULL) { + intermediatefd = fd; + fd = -1; + if (virExec(conn, intermediate_argv, NULL, NULL, + &intermediate_pid, intermediatefd, &fd, NULL, 0) < 0) { + qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, + _("Failed to start decompression binary %s"), + intermediate_argv[0]); + goto cleanup; + } + } + } /* Set the migration source and start it up. */ ret = qemudStartVMDaemon(conn, driver, vm, "stdio", fd); + if (intermediate_pid != -1) { + /* Wait for intermediate process to exit */ + while (waitpid(intermediate_pid, &childstat, 0) == -1 && + errno == EINTR); + } + if (intermediatefd != -1) + close(intermediatefd); close(fd); fd = -1; if (ret < 0) { -- 1.6.0.6

Chris Lalancette wrote:
+ if (header.compressed == QEMUD_SAVE_FORMAT_RAW) + internalret = virAsprintf(&command, "migrate \"exec:" + "dd of='%s' oflag=append conv=notrunc 2>/dev/null" + "\"", safe_path); + else if (header.compressed == QEMUD_SAVE_FORMAT_GZIP) + internalret = virAsprintf(&command, "migrate \"exec:" + "gzip -c | dd of='%s' oflag=append conv=notrunc 2>/dev/null" + "\"", safe_path); + else if (header.compressed == QEMUD_SAVE_FORMAT_BZIP2) + internalret = virAsprintf(&command, "migrate \"exec:" + "bzip2 -c | dd of='%s' oflag=append conv=notrunc 2>/dev/null" + "\"", safe_path);
Heh. On IRC DanB pointed out that my use of dd here is redundant; I should just be able to redirect with >>. NACK to this patch, I'll post a new one. -- Chris Lalancette

If CPU time is one of the major disincentives towards use of compression -- any reason lzop wasn't included? $ time lzop <ramsave >ramsave.lzo real 0m13.515s user 0m7.500s sys 0m1.340s $ time gzip -c <ramsave >ramsave.gz real 0m46.327s user 0m37.690s sys 0m1.360s $ ls -lh ramsave ramsave.lzo ramsave.gz -rw-rw---- 1 fvte fvte 707M Aug 26 22:26 ramsave -rw-r--r-- 1 root fvte 282M Aug 27 17:48 ramsave.gz -rw-rw---- 1 fvte fvte 330M Aug 27 17:47 ramsave.lzo

On Thu, Aug 27, 2009 at 05:47:13PM -0500, Charles Duffy wrote:
If CPU time is one of the major disincentives towards use of compression -- any reason lzop wasn't included?
Never heard of it before - if its useful to support it, then do send patches...
$ time lzop <ramsave >ramsave.lzo real 0m13.515s user 0m7.500s sys 0m1.340s $ time gzip -c <ramsave >ramsave.gz real 0m46.327s user 0m37.690s sys 0m1.360s $ ls -lh ramsave ramsave.lzo ramsave.gz -rw-rw---- 1 fvte fvte 707M Aug 26 22:26 ramsave -rw-r--r-- 1 root fvte 282M Aug 27 17:48 ramsave.gz -rw-rw---- 1 fvte fvte 330M Aug 27 17:47 ramsave.lzo
Regards, Daniel -- |: Red Hat, Engineering, London -o- http://people.redhat.com/berrange/ :| |: http://libvirt.org -o- http://virt-manager.org -o- http://ovirt.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: GnuPG: 7D3B9505 -o- F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 :|
participants (3)
-
Charles Duffy
-
Chris Lalancette
-
Daniel P. Berrange