[libvirt] [PATCH 3/4] qemu_migration: Check size prerequisites

Michal Privoznik mprivozn at redhat.com
Thu Sep 12 10:15:18 UTC 2013


With new NBD storage migration approach there are several
requirements that need to be meet for successful use of the
feature. One of them is - the file representing a disk, needs to
have at least same size as on the source. Hence, we must transfer
a list of pairs [disk target, size] and check on destination that
this requirement is met and/or take actions to meet it.

Signed-off-by: Michal Privoznik <mprivozn at redhat.com>
---
 src/qemu/qemu_migration.c | 286 +++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 281 insertions(+), 5 deletions(-)

diff --git a/src/qemu/qemu_migration.c b/src/qemu/qemu_migration.c
index 69a9013..5080b0a 100644
--- a/src/qemu/qemu_migration.c
+++ b/src/qemu/qemu_migration.c
@@ -31,6 +31,9 @@
 #endif
 #include <fcntl.h>
 #include <poll.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
 
 #include "qemu_migration.h"
 #include "qemu_monitor.h"
@@ -135,6 +138,15 @@ typedef struct _qemuMigrationCookieNBD qemuMigrationCookieNBD;
 typedef qemuMigrationCookieNBD *qemuMigrationCookieNBDPtr;
 struct _qemuMigrationCookieNBD {
     int port; /* on which port does NBD server listen for incoming data */
+
+    /* The list of pairs [disk, size] (in Bytes).
+     * This is needed because the same disk size is one of
+     * prerequisites for NBD storage migration. */
+    size_t ndisks;
+    struct {
+        char *target;
+        size_t bytes;
+    } *disk;
 };
 
 typedef struct _qemuMigrationCookie qemuMigrationCookie;
@@ -197,6 +209,22 @@ qemuMigrationCookieNetworkFree(qemuMigrationCookieNetworkPtr network)
 }
 
 
+static void
+qemuMigrationCookieNBDFree(qemuMigrationCookieNBDPtr nbd)
+{
+    size_t i;
+
+    if (!nbd)
+        return;
+
+    for (i = 0; i < nbd->ndisks; i++)
+        VIR_FREE(nbd->disk[i].target);
+
+    VIR_FREE(nbd->disk);
+    VIR_FREE(nbd);
+}
+
+
 static void qemuMigrationCookieFree(qemuMigrationCookiePtr mig)
 {
     if (!mig)
@@ -208,12 +236,14 @@ static void qemuMigrationCookieFree(qemuMigrationCookiePtr mig)
     if (mig->flags & QEMU_MIGRATION_COOKIE_NETWORK)
         qemuMigrationCookieNetworkFree(mig->network);
 
+    if (mig->flags & QEMU_MIGRATION_COOKIE_NBD)
+        qemuMigrationCookieNBDFree(mig->nbd);
+
     VIR_FREE(mig->localHostname);
     VIR_FREE(mig->remoteHostname);
     VIR_FREE(mig->name);
     VIR_FREE(mig->lockState);
     VIR_FREE(mig->lockDriver);
-    VIR_FREE(mig->nbd);
     VIR_FREE(mig);
 }
 
@@ -518,20 +548,58 @@ qemuMigrationCookieAddNetwork(qemuMigrationCookiePtr mig,
 
 static int
 qemuMigrationCookieAddNBD(qemuMigrationCookiePtr mig,
-                          virQEMUDriverPtr driver ATTRIBUTE_UNUSED,
+                          virQEMUDriverPtr driver,
                           virDomainObjPtr vm)
 {
     qemuDomainObjPrivatePtr priv = vm->privateData;
+    int ret = -1;
+    size_t i;
 
     /* It is not a bug if there already is a NBD data */
     if (!mig->nbd &&
         VIR_ALLOC(mig->nbd) < 0)
-        return -1;
+        return ret;
+
+    /* in Begin phase add info about disks */
+    if (priv->job.phase == QEMU_MIGRATION_PHASE_BEGIN3 &&
+        vm->def->ndisks) {
+        if (VIR_ALLOC_N(mig->nbd->disk, vm->def->ndisks) < 0)
+            goto cleanup;
+
+        for (i = 0; i < vm->def->ndisks; i++) {
+            virDomainDiskDefPtr disk = vm->def->disks[i];
+            virDomainBlockInfo info;
+
+            /* Add only non-shared RW disks with source */
+            if (!disk->src || disk->shared || disk->readonly)
+                continue;
+
+            memset(&info, 0, sizeof(info));
+
+            if (qemuDomainGetDiskBlockInfo(driver, vm, disk, &info) < 0)
+                goto cleanup;
+
+            /* Explicitly not checking which formats can be pre-created here,
+             * as we might be talking to newer libvirt which knows more than we
+             * do now in here. Just let the destination libvirt decide. */
+            if (VIR_STRDUP(mig->nbd->disk[mig->nbd->ndisks].target, disk->dst) < 0)
+                goto cleanup;
+
+            mig->nbd->disk[mig->nbd->ndisks].bytes = info.capacity;
+            mig->nbd->ndisks++;
+        }
+    }
 
     mig->nbd->port = priv->nbdPort;
     mig->flags |= QEMU_MIGRATION_COOKIE_NBD;
 
-    return 0;
+    ret = 0;
+
+cleanup:
+    if (ret < 0)
+        qemuMigrationCookieNBDFree(mig->nbd);
+
+    return ret;
 }
 
 
@@ -641,7 +709,16 @@ qemuMigrationCookieXMLFormat(virQEMUDriverPtr driver,
         virBufferAddLit(buf, "  <nbd");
         if (mig->nbd->port)
             virBufferAsprintf(buf, " port='%d'", mig->nbd->port);
-        virBufferAddLit(buf, "/>\n");
+        if (mig->nbd->ndisks) {
+            virBufferAddLit(buf, ">\n");
+            for (i = 0; i < mig->nbd->ndisks; i++)
+                virBufferAsprintf(buf, "    <disk target='%s' size='%zu'/>\n",
+                                  mig->nbd->disk[i].target,
+                                  mig->nbd->disk[i].bytes);
+            virBufferAddLit(buf, "  </nbd>\n");
+        } else {
+            virBufferAddLit(buf, "/>\n");
+        }
     }
 
     virBufferAddLit(buf, "</qemu-migration>\n");
@@ -941,6 +1018,44 @@ qemuMigrationCookieXMLParse(qemuMigrationCookiePtr mig,
             goto error;
         }
         VIR_FREE(port);
+
+        if ((n = virXPathNodeSet("./nbd/disk", ctxt, &nodes)) > 0) {
+            xmlNodePtr oldNode = ctxt->node;
+            if (VIR_ALLOC_N(mig->nbd->disk, n) < 0) {
+                virReportOOMError();
+                goto error;
+            }
+            mig->nbd->ndisks = n;
+
+            for (i = 0; i < n; i++) {
+                ctxt->node = nodes[i];
+
+                tmp = virXPathString("string(./@target)", ctxt);
+                if (!tmp) {
+                    virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                                   _("Malformed target attribute"));
+                    goto error;
+                }
+                mig->nbd->disk[i].target = tmp;
+                /* deliberately don't free @tmp here, as the
+                 * cookie has the reference now and it is
+                 * responsible for freeing it later */
+
+                tmp = virXPathString("string(./@size)", ctxt);
+                if (virStrToLong_ull(tmp, NULL, 10, (unsigned long long *)
+                                     &mig->nbd->disk[i].bytes) < 0) {
+                    virReportError(VIR_ERR_INTERNAL_ERROR,
+                                   _("Malformed size attribute '%s'"),
+                                   tmp);
+                    VIR_FREE(tmp);
+                    goto error;
+                }
+                VIR_FREE(tmp);
+            }
+            VIR_FREE(nodes);
+            ctxt->node = oldNode;
+        }
+        VIR_FREE(port);
     }
 
     virObjectUnref(caps);
@@ -1389,6 +1504,163 @@ cleanup:
     return;
 }
 
+static int
+qemuMigrationPreCreateStorageRaw(virQEMUDriverPtr driver,
+                                 virDomainObjPtr vm,
+                                 virDomainDiskDefPtr disk,
+                                 size_t bytes)
+{
+    int ret = -1;
+    struct stat sb;
+    int fd = -1;
+    bool need_unlink = false;
+
+    if ((fd = qemuOpenFile(driver, vm, disk->src, O_RDWR | O_CREAT,
+                           &need_unlink, NULL)) < 0)
+        goto cleanup;
+
+    if (fstat(fd, &sb) < 0) {
+        virReportSystemError(errno, _("Unable to stat '%s'"), disk->src);
+        goto cleanup;
+    }
+
+    VIR_DEBUG("File '%s' is %zuB big. Required %zuB", disk->src, sb.st_size, bytes);
+    if (sb.st_size != bytes &&
+        posix_fallocate(fd, 0, bytes) < 0) {
+        virReportSystemError(errno, _("Unable to fallocate '%s'"), disk->src);
+        goto cleanup;
+    }
+
+    ret = 0;
+
+cleanup:
+    VIR_FORCE_CLOSE(fd);
+    if (ret < 0 && need_unlink && unlink(disk->src))
+        VIR_WARN("Unable to unlink '%s': %d", disk->src, errno);
+    return ret;
+}
+
+static int
+qemuMigrationPreCreateStorageQCOW(virQEMUDriverPtr driver,
+                                  virDomainDiskDefPtr disk,
+                                  int format,
+                                  size_t bytes)
+{
+    int ret = -1;
+    const char *imgBinary = qemuFindQemuImgBinary(driver);
+    virCommandPtr cmd = NULL;
+    size_t size_arg;
+
+    if (!imgBinary)
+        return ret;
+
+    /* Size in KB */
+    size_arg = VIR_DIV_UP(bytes, 1024);
+
+    cmd = virCommandNewArgList(imgBinary, "create", "-f",
+                              virStorageFileFormatTypeToString(format),
+                              "-o", "preallocation=metadata",
+                              disk->src, NULL);
+
+    virCommandAddArgFormat(cmd, "%zuK", size_arg);
+
+    if (virCommandRun(cmd, NULL) < 0)
+        goto unlink;
+
+    ret = 0;
+
+cleanup:
+    return ret;
+
+unlink:
+    if (unlink(disk->src) < 0)
+        VIR_WARN("Unable to unlink '%s': %d", disk->src, errno);
+    goto cleanup;
+}
+
+static int
+qemuMigrationPreCreateStorage(virQEMUDriverPtr driver,
+                              virDomainObjPtr vm,
+                              qemuMigrationCookiePtr mig)
+{
+    int ret = -1;
+    size_t i = 0;
+
+    if (!mig->nbd || !mig->nbd->ndisks) {
+        /* nothing to do here */
+        return 0;
+    }
+
+    for (i = 0; i < mig->nbd->ndisks; i++) {
+        virDomainDiskDefPtr disk;
+        int format;
+        size_t bytes;
+        int index;
+
+        index = virDomainDiskIndexByName(vm->def, mig->nbd->disk[i].target, false);
+        if (index < 0) {
+            virReportError(VIR_ERR_INTERNAL_ERROR,
+                           _("No such disk '%s"),
+                           mig->nbd->disk[i].target);
+            goto cleanup;
+        }
+
+        disk = vm->def->disks[i];
+        format = disk->format;
+        bytes = mig->nbd->disk[i].bytes;
+
+        VIR_DEBUG("Checking '%s' of %s format for its size (requested %zuB)",
+                  disk->src,  virStorageFileFormatTypeToString(format), bytes);
+        switch ((enum virStorageFileFormat) format) {
+            /* These below we know how to pre-create. */
+        case VIR_STORAGE_FILE_RAW:
+            if (qemuMigrationPreCreateStorageRaw(driver, vm, disk, bytes) < 0)
+                goto cleanup;
+            break;
+        case VIR_STORAGE_FILE_QCOW:
+        case VIR_STORAGE_FILE_QCOW2:
+            if (qemuMigrationPreCreateStorageQCOW(driver, disk,
+                                                  format, bytes) < 0)
+                goto cleanup;
+            break;
+
+            /* While these we don't know yet. */
+        case VIR_STORAGE_FILE_AUTO_SAFE:
+        case VIR_STORAGE_FILE_AUTO:
+        case VIR_STORAGE_FILE_NONE:
+        case VIR_STORAGE_FILE_DIR:
+        case VIR_STORAGE_FILE_BOCHS:
+        case VIR_STORAGE_FILE_CLOOP:
+        case VIR_STORAGE_FILE_COW:
+        case VIR_STORAGE_FILE_DMG:
+        case VIR_STORAGE_FILE_ISO:
+        case VIR_STORAGE_FILE_QED:
+        case VIR_STORAGE_FILE_VMDK:
+        case VIR_STORAGE_FILE_VPC:
+        case VIR_STORAGE_FILE_FAT:
+        case VIR_STORAGE_FILE_VHD:
+        case VIR_STORAGE_FILE_VDI:
+        case VIR_STORAGE_FILE_LAST:
+            /* Should we error here? As long as user has no control over which
+             * disks are copied (currently there is no way specifying only a
+             * set of disks to copy) we can't error here. What we can do is
+             * leave users with old migration prerequisite: You (users) are
+             * responsible for creating the storage on the destination. */
+            VIR_WARN("Don't know how to pre-create disks of %s type ('%s')",
+                     virStorageFileFormatTypeToString(format), disk->src);
+            break;
+        }
+    }
+
+    ret = 0;
+cleanup:
+    /* free from migration data to prevent
+     * infinite sending from src to dst and back */
+    VIR_FREE(mig->nbd->disk);
+    mig->nbd->ndisks = 0;
+    return ret;
+}
+
 /* Validate whether the domain is safe to migrate.  If vm is NULL,
  * then this is being run in the v2 Prepare stage on the destination
  * (where we only have the target xml); if vm is provided, then this
@@ -2305,6 +2577,10 @@ qemuMigrationPrepareAny(virQEMUDriverPtr driver,
                                        QEMU_MIGRATION_COOKIE_NBD)))
         goto cleanup;
 
+    /* pre-create all storage */
+    if (qemuMigrationPreCreateStorage(driver, vm, mig) < 0)
+        goto cleanup;
+
     if (qemuMigrationJobStart(driver, vm, QEMU_ASYNC_JOB_MIGRATION_IN) < 0)
         goto cleanup;
     qemuMigrationJobSetPhase(driver, vm, QEMU_MIGRATION_PHASE_PREPARE);
-- 
1.8.1.5




More information about the libvir-list mailing list