[libvirt] [PATCH v2 04/22] qemu: Use domain condition for synchronous block jobs

Peter Krempa pkrempa at redhat.com
Tue Jun 9 16:11:09 UTC 2015


On Tue, Jun 02, 2015 at 14:34:09 +0200, Jiri Denemark wrote:
> By switching block jobs to use domain conditions, we can drop some
> pretty complicated code in NBD storage migration. Moreover, we are
> getting ready for migration events (to replace our 50ms polling on
> query-migrate). The ultimate goal is to have a single loop waiting
> (virDomainObjWait) for any migration related event (changed status of a
> migration, disk mirror events, internal abort requests, ...). This patch
> makes NBD storage migration ready for this: first we call a QMP command
> to start or cancel drive mirror on all disks we are interested in and
> then we wait for a single condition which is signaled on any event
> related to any of the mirrors.
> 
> Signed-off-by: Jiri Denemark <jdenemar at redhat.com>
> ---
> 
> Notes:
>     Version 2:
>     - slightly modified to use domain conditions
> 
>  po/POTFILES.in            |   1 -
>  src/qemu/qemu_blockjob.c  | 137 ++-------------------
>  src/qemu/qemu_blockjob.h  |  12 +-
>  src/qemu/qemu_domain.c    |  17 +--
>  src/qemu/qemu_domain.h    |   1 -
>  src/qemu/qemu_driver.c    |  24 ++--
>  src/qemu/qemu_migration.c | 299 ++++++++++++++++++++++++++--------------------
>  src/qemu/qemu_process.c   |  13 +-
>  8 files changed, 197 insertions(+), 307 deletions(-)
> 

...

> diff --git a/src/qemu/qemu_migration.c b/src/qemu/qemu_migration.c
> index 93e29e7..61b3e34 100644
> --- a/src/qemu/qemu_migration.c
> +++ b/src/qemu/qemu_migration.c

...

> @@ -1733,111 +1733,148 @@ qemuMigrationStopNBDServer(virQEMUDriverPtr driver,
>   *        -1 on error.
>   */
>  static int
> -qemuMigrationCheckDriveMirror(virQEMUDriverPtr driver,
> +qemuMigrationDriveMirrorReady(virQEMUDriverPtr driver,
>                                virDomainObjPtr vm)
>  {
>      size_t i;
> -    int ret = 1;
> +    size_t notReady = 0;
> +    int status;
>  
>      for (i = 0; i < vm->def->ndisks; i++) {
>          virDomainDiskDefPtr disk = vm->def->disks[i];
>          qemuDomainDiskPrivatePtr diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk);
>  
> -        if (!diskPriv->migrating || !diskPriv->blockJobSync)
> +        if (!diskPriv->migrating)
>              continue;
>  
> -        /* process any pending event */
> -        if (qemuBlockJobSyncWaitWithTimeout(driver, vm, disk,
> -                                            0ull, NULL) < 0)
> -            return -1;
> -
> -        switch (disk->mirrorState) {
> -        case VIR_DOMAIN_DISK_MIRROR_STATE_NONE:
> -            ret = 0;
> -            break;
> -        case VIR_DOMAIN_DISK_MIRROR_STATE_ABORT:
> +        status = qemuBlockJobUpdate(driver, vm, disk);
> +        if (status == VIR_DOMAIN_BLOCK_JOB_FAILED) {
>              virReportError(VIR_ERR_OPERATION_FAILED,
>                             _("migration of disk %s failed"),
>                             disk->dst);
>              return -1;
>          }
> +
> +        if (disk->mirrorState != VIR_DOMAIN_DISK_MIRROR_STATE_READY)
> +            notReady++;
>      }
>  
> -    return ret;
> +    if (notReady) {
> +        VIR_DEBUG("Waiting for %zu disk mirrors to get ready", notReady);
> +        return 0;
> +    } else {
> +        VIR_DEBUG("All disk mirrors are ready");
> +        return 1;
> +    }
>  }
>  
>  
> -/**
> - * qemuMigrationCancelOneDriveMirror:
> - * @driver: qemu driver
> - * @vm: domain
> +/*
> + * If @failed is not NULL, the function will report an error and set @failed
> + * to true in case a block job fails. This way we can properly abort migration
> + * in case some block job failed once all memory has already been transferred.
>   *
> - * Cancel all drive-mirrors started by qemuMigrationDriveMirror.
> - * Any pending block job events for the mirrored disks will be
> - * processed.
> - *
> - * Returns 0 on success, -1 otherwise.
> + * Returns 1 if all mirrors are gone,
> + *         0 if some mirrors are still active,
> + *         -1 on error.


The code below doesn't ever return -1. Perhaps you could use it instead
of passing the pointer.

>   */
>  static int
> -qemuMigrationCancelOneDriveMirror(virQEMUDriverPtr driver,
> +qemuMigrationDriveMirrorCancelled(virQEMUDriverPtr driver,
>                                    virDomainObjPtr vm,
> -                                  virDomainDiskDefPtr disk)
> +                                  bool *failed)
>  {
> -    qemuDomainObjPrivatePtr priv = vm->privateData;
> -    char *diskAlias = NULL;
> -    int ret = -1;
> +    size_t i;
> +    size_t active = 0;
> +    int status;
>  
> -    /* No need to cancel if mirror already aborted */
> -    if (disk->mirrorState == VIR_DOMAIN_DISK_MIRROR_STATE_ABORT) {
> -        ret = 0;
> -    } else {
> -        virConnectDomainEventBlockJobStatus status = -1;
> +    for (i = 0; i < vm->def->ndisks; i++) {
> +        virDomainDiskDefPtr disk = vm->def->disks[i];
> +        qemuDomainDiskPrivatePtr diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk);
>  
> -        if (virAsprintf(&diskAlias, "%s%s",
> -                        QEMU_DRIVE_HOST_PREFIX, disk->info.alias) < 0)
> -            goto cleanup;
> +        if (!diskPriv->migrating)
> +            continue;
>  
> -        if (qemuDomainObjEnterMonitorAsync(driver, vm,
> -                                           QEMU_ASYNC_JOB_MIGRATION_OUT) < 0)
> -            goto endjob;
> -        ret = qemuMonitorBlockJobCancel(priv->mon, diskAlias, true);
> -        if (qemuDomainObjExitMonitor(driver, vm) < 0)
> -            goto endjob;
> -
> -        if (ret < 0) {
> -            virDomainBlockJobInfo info;
> -
> -            /* block-job-cancel can fail if QEMU simultaneously
> -             * aborted the job; probe for it again to detect this */
> -            if (qemuMonitorBlockJobInfo(priv->mon, diskAlias,
> -                                        &info, NULL) == 0) {
> -                ret = 0;
> -            } else {
> +        status = qemuBlockJobUpdate(driver, vm, disk);
> +        switch (status) {
> +        case VIR_DOMAIN_BLOCK_JOB_FAILED:
> +            if (failed) {
>                  virReportError(VIR_ERR_OPERATION_FAILED,
> -                               _("could not cancel migration of disk %s"),
> +                               _("migration of disk %s failed"),
>                                 disk->dst);
> +                *failed = true;
>              }
> +            /* fallthrough */
> +        case VIR_DOMAIN_BLOCK_JOB_CANCELED:
> +        case VIR_DOMAIN_BLOCK_JOB_COMPLETED:
> +            qemuBlockJobSyncEnd(driver, vm, disk);
> +            diskPriv->migrating = false;
> +            break;
>  
> -            goto endjob;
> +        default:
> +            active++;
>          }
> +    }
>  
> -        /* Mirror may become ready before cancellation takes
> -         * effect; loop if we get that event first */
> -        do {
> -            ret = qemuBlockJobSyncWait(driver, vm, disk, &status);
> -            if (ret < 0) {
> -                VIR_WARN("Unable to wait for block job on %s to cancel",
> -                         diskAlias);
> -                goto endjob;
> -            }
> -        } while (status == VIR_DOMAIN_BLOCK_JOB_READY);
> +    if (active) {
> +        VIR_DEBUG("Waiting for %zu disk mirrors to finish", active);
> +        return 0;
> +    } else {
> +        if (failed && *failed)
> +            VIR_DEBUG("All disk mirrors are gone; some of them failed");
> +        else
> +            VIR_DEBUG("All disk mirrors are gone");
> +        return 1;
>      }
> +}
> +
> +
...

> @@ -2467,6 +2515,10 @@ qemuMigrationWaitForCompletion(virQEMUDriverPtr driver,
>          if (qemuMigrationUpdateJobStatus(driver, vm, job, asyncJob) == -1)
>              goto error;
>  
> +        if (storage &&
> +            qemuMigrationDriveMirrorReady(driver, vm) < 0)
> +            break;

I think this hunk and the related changes should be in a separate patch.
This code preparses for changing to a event based migration finish,
while the rest of the patch touches mostly other parts that don't deal
with the big picture of migration.

> +
>          /* cancel migration if disk I/O error is emitted while migrating */
>          if (abort_on_error &&
>              virDomainObjGetState(vm, &pauseReason) == VIR_DOMAIN_PAUSED &&

...

> diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c
> index 9c5d0f4..a945401 100644
> --- a/src/qemu/qemu_process.c
> +++ b/src/qemu/qemu_process.c
> @@ -1001,10 +1001,10 @@ qemuProcessHandleBlockJob(qemuMonitorPtr mon ATTRIBUTE_UNUSED,
>      diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk);
>  
>      if (diskPriv->blockJobSync) {
> +        /* We have a SYNC API waiting for this event, dispatch it back */
>          diskPriv->blockJobType = type;
>          diskPriv->blockJobStatus = status;
> -        /* We have an SYNC API waiting for this event, dispatch it back */
> -        virCondSignal(&diskPriv->blockJobSyncCond);
> +        virDomainObjSignal(vm);

Once there will be a possibility to have more waiting threads for a
certain events we might need to revisit these.

>      } else {
>          /* there is no waiting SYNC API, dispatch the update to a thread */
>          if (VIR_ALLOC(processEvent) < 0)


Looks good but I'd rather see a version that avoids doing both the
refactor to the new condition and preparation to use events for
migration.

Peter
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 819 bytes
Desc: Digital signature
URL: <http://listman.redhat.com/archives/libvir-list/attachments/20150609/a0a9a0a3/attachment-0001.sig>


More information about the libvir-list mailing list