/* * qemu_migration.c: QEMU migration handling * * Copyright (C) 2006-2015 Red Hat, Inc. * * 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 #include #include #include "qemu_migration.h" #include "qemu_migration_cookie.h" #include "qemu_migration_params.h" #include "qemu_monitor.h" #include "qemu_domain.h" #include "qemu_process.h" #include "qemu_capabilities.h" #include "qemu_alias.h" #include "qemu_hotplug.h" #include "qemu_blockjob.h" #include "qemu_security.h" #include "qemu_slirp.h" #include "qemu_block.h" #include "qemu_tpm.h" #include "domain_audit.h" #include "virlog.h" #include "virerror.h" #include "viralloc.h" #include "virfile.h" #include "virnetdevopenvswitch.h" #include "datatypes.h" #include "virfdstream.h" #include "virtime.h" #include "rpc/virnetsocket.h" #include "storage_source_conf.h" #include "viruri.h" #include "virhook.h" #include "virstring.h" #include "virtypedparam.h" #include "virprocess.h" #include "virdomainsnapshotobjlist.h" #include "virutil.h" #define VIR_FROM_THIS VIR_FROM_QEMU VIR_LOG_INIT("qemu.qemu_migration"); VIR_ENUM_IMPL(qemuMigrationJobPhase, QEMU_MIGRATION_PHASE_LAST, "none", "perform2", "begin3", "perform3", "perform3_done", "confirm3_cancelled", "confirm3", "prepare", "finish2", "finish3", "postcopy_failed", "begin_resume", "perform_resume", "confirm_resume", "prepare_resume", "finish_resume", ); static bool ATTRIBUTE_NONNULL(1) qemuMigrationJobIsAllowed(virDomainObj *vm) { if (vm->job->asyncJob == VIR_ASYNC_JOB_MIGRATION_IN || vm->job->asyncJob == VIR_ASYNC_JOB_MIGRATION_OUT) { virReportError(VIR_ERR_OPERATION_INVALID, _("another migration job is already running for domain '%1$s'"), vm->def->name); return false; } return true; } static int ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) G_GNUC_WARN_UNUSED_RESULT qemuMigrationJobStart(virDomainObj *vm, virDomainAsyncJob job, unsigned int apiFlags) { virDomainJobOperation op; unsigned long long mask; if (!qemuMigrationJobIsAllowed(vm)) return -1; if (job == VIR_ASYNC_JOB_MIGRATION_IN) { op = VIR_DOMAIN_JOB_OPERATION_MIGRATION_IN; mask = VIR_JOB_NONE; } else { op = VIR_DOMAIN_JOB_OPERATION_MIGRATION_OUT; mask = VIR_JOB_DEFAULT_MASK | JOB_MASK(VIR_JOB_SUSPEND) | JOB_MASK(VIR_JOB_MIGRATION_OP); } mask |= JOB_MASK(VIR_JOB_MODIFY_MIGRATION_SAFE); if (virDomainObjBeginAsyncJob(vm, job, op, apiFlags) < 0) return -1; qemuDomainJobSetStatsType(vm->job->current, QEMU_DOMAIN_JOB_STATS_TYPE_MIGRATION); qemuDomainObjSetAsyncJobMask(vm, mask); return 0; } static int qemuMigrationCheckPhase(virDomainObj *vm, qemuMigrationJobPhase phase) { if (phase < QEMU_MIGRATION_PHASE_POSTCOPY_FAILED && phase < vm->job->phase) { virReportError(VIR_ERR_INTERNAL_ERROR, _("migration protocol going backwards %1$s => %2$s"), qemuMigrationJobPhaseTypeToString(vm->job->phase), qemuMigrationJobPhaseTypeToString(phase)); return -1; } return 0; } static int G_GNUC_WARN_UNUSED_RESULT qemuMigrationJobSetPhase(virDomainObj *vm, qemuMigrationJobPhase phase) { if (qemuMigrationCheckPhase(vm, phase) < 0) return -1; qemuDomainObjSetJobPhase(vm, phase); return 0; } static int G_GNUC_WARN_UNUSED_RESULT qemuMigrationJobStartPhase(virDomainObj *vm, qemuMigrationJobPhase phase) { if (qemuMigrationCheckPhase(vm, phase) < 0) return -1; qemuDomainObjStartJobPhase(vm, phase); return 0; } static void ATTRIBUTE_NONNULL(1) qemuMigrationJobContinue(virDomainObj *vm, qemuDomainCleanupCallback cleanup) { qemuDomainCleanupAdd(vm, cleanup); qemuDomainObjReleaseAsyncJob(vm); } static bool ATTRIBUTE_NONNULL(1) qemuMigrationJobIsActive(virDomainObj *vm, virDomainAsyncJob job) { if (vm->job->asyncJob != job) { const char *msg; if (job == VIR_ASYNC_JOB_MIGRATION_IN) msg = _("domain '%1$s' is not processing incoming migration"); else msg = _("domain '%1$s' is not being migrated"); virReportError(VIR_ERR_OPERATION_INVALID, msg, vm->def->name); return false; } return true; } static void ATTRIBUTE_NONNULL(1) qemuMigrationJobFinish(virDomainObj *vm) { virDomainObjEndAsyncJob(vm); } static void qemuMigrationSrcStoreDomainState(virDomainObj *vm) { qemuDomainObjPrivate *priv = vm->privateData; priv->preMigrationState = virDomainObjGetState(vm, NULL); VIR_DEBUG("Storing pre-migration state=%d domain=%p", priv->preMigrationState, vm); } /* Returns true if the domain was resumed, false otherwise */ static bool qemuMigrationSrcRestoreDomainState(virQEMUDriver *driver, virDomainObj *vm) { qemuDomainObjPrivate *priv = vm->privateData; int reason; virDomainState state = virDomainObjGetState(vm, &reason); bool ret = false; VIR_DEBUG("driver=%p, vm=%p, pre-mig-state=%s, state=%s, reason=%s", driver, vm, virDomainStateTypeToString(priv->preMigrationState), virDomainStateTypeToString(state), virDomainStateReasonToString(state, reason)); if (state != VIR_DOMAIN_PAUSED || reason == VIR_DOMAIN_PAUSED_POSTCOPY_FAILED) goto cleanup; if (priv->preMigrationState == VIR_DOMAIN_RUNNING) { /* This is basically the only restore possibility that's safe * and we should attempt to do */ VIR_DEBUG("Restoring pre-migration state due to migration error"); /* we got here through some sort of failure; start the domain again */ if (qemuProcessStartCPUs(driver, vm, VIR_DOMAIN_RUNNING_MIGRATION_CANCELED, VIR_ASYNC_JOB_MIGRATION_OUT) < 0) { /* Hm, we already know we are in error here. We don't want to * overwrite the previous error, though, so we just throw something * to the logs and hope for the best */ VIR_ERROR(_("Failed to resume guest %1$s after failure"), vm->def->name); if (virDomainObjGetState(vm, NULL) == VIR_DOMAIN_PAUSED) { virObjectEvent *event; virDomainObjSetState(vm, VIR_DOMAIN_PAUSED, VIR_DOMAIN_PAUSED_API_ERROR); event = virDomainEventLifecycleNewFromObj(vm, VIR_DOMAIN_EVENT_SUSPENDED, VIR_DOMAIN_EVENT_SUSPENDED_API_ERROR); virObjectEventStateQueue(driver->domainEventState, event); } goto cleanup; } ret = true; } cleanup: priv->preMigrationState = VIR_DOMAIN_NOSTATE; return ret; } static int qemuMigrationDstPrecreateDisk(virConnectPtr *conn, virDomainDiskDef *disk, unsigned long long capacity) { int ret = -1; virStoragePoolPtr pool = NULL; virStorageVolPtr vol = NULL; char *volName = NULL, *basePath = NULL; char *volStr = NULL; g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER; const char *format = NULL; const char *compat = NULL; unsigned int flags = 0; VIR_DEBUG("Precreate disk type=%s", virStorageTypeToString(disk->src->type)); switch (disk->src->type) { case VIR_STORAGE_TYPE_FILE: if (!virDomainDiskGetSource(disk)) { VIR_DEBUG("Dropping sourceless disk '%s'", disk->dst); return 0; } basePath = g_strdup(disk->src->path); if (!(volName = strrchr(basePath, '/'))) { virReportError(VIR_ERR_INVALID_ARG, _("malformed disk path: %1$s"), disk->src->path); goto cleanup; } *volName = '\0'; volName++; if (!*conn) { if (!(*conn = virGetConnectStorage())) goto cleanup; } if (!(pool = virStoragePoolLookupByTargetPath(*conn, basePath))) goto cleanup; format = virStorageFileFormatTypeToString(disk->src->format); if (disk->src->format == VIR_STORAGE_FILE_QCOW2) { flags |= VIR_STORAGE_VOL_CREATE_PREALLOC_METADATA; /* format qcow2v3 image */ compat = "1.1"; } break; case VIR_STORAGE_TYPE_VOLUME: if (!*conn) { if (!(*conn = virGetConnectStorage())) goto cleanup; } if (!(pool = virStoragePoolLookupByName(*conn, disk->src->srcpool->pool))) goto cleanup; format = virStorageFileFormatTypeToString(disk->src->format); volName = disk->src->srcpool->volume; if (disk->src->format == VIR_STORAGE_FILE_QCOW2) flags |= VIR_STORAGE_VOL_CREATE_PREALLOC_METADATA; break; case VIR_STORAGE_TYPE_NETWORK: VIR_DEBUG("Skipping creation of network disk '%s'", disk->dst); return 0; case VIR_STORAGE_TYPE_BLOCK: case VIR_STORAGE_TYPE_DIR: case VIR_STORAGE_TYPE_NVME: case VIR_STORAGE_TYPE_VHOST_USER: case VIR_STORAGE_TYPE_NONE: case VIR_STORAGE_TYPE_LAST: virReportError(VIR_ERR_INTERNAL_ERROR, _("cannot precreate storage for disk type '%1$s'"), virStorageTypeToString(disk->src->type)); goto cleanup; } if ((vol = virStorageVolLookupByName(pool, volName))) { VIR_DEBUG("Skipping creation of already existing volume of name '%s'", volName); ret = 0; goto cleanup; } virBufferAddLit(&buf, "\n"); virBufferAdjustIndent(&buf, 2); virBufferEscapeString(&buf, "%s\n", volName); virBufferAsprintf(&buf, "%llu\n", capacity); virBufferAddLit(&buf, "\n"); virBufferAdjustIndent(&buf, 2); virBufferAsprintf(&buf, "\n", format); if (compat) virBufferAsprintf(&buf, "%s\n", compat); virBufferAdjustIndent(&buf, -2); virBufferAddLit(&buf, "\n"); virBufferAdjustIndent(&buf, -2); virBufferAddLit(&buf, "\n"); if (!(volStr = virBufferContentAndReset(&buf))) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("unable to create volume XML")); goto cleanup; } if (!(vol = virStorageVolCreateXML(pool, volStr, flags))) goto cleanup; ret = 0; cleanup: VIR_FREE(basePath); VIR_FREE(volStr); virObjectUnref(vol); virObjectUnref(pool); return ret; } static bool qemuMigrationAnyCopyDisk(virDomainDiskDef const *disk, size_t nmigrate_disks, const char **migrate_disks) { size_t i; /* Check if the disk alias is in the list */ if (nmigrate_disks) { for (i = 0; i < nmigrate_disks; i++) { if (STREQ(disk->dst, migrate_disks[i])) return true; } return false; } /* Default is to migrate only non-shared non-readonly disks * with source */ return !disk->src->shared && !disk->src->readonly && !virStorageSourceIsEmpty(disk->src); } static bool qemuMigrationHasAnyStorageMigrationDisks(virDomainDef *def, const char **migrate_disks, size_t nmigrate_disks) { size_t i; for (i = 0; i < def->ndisks; i++) { if (qemuMigrationAnyCopyDisk(def->disks[i], nmigrate_disks, migrate_disks)) return true; } return false; } static int qemuMigrationDstPrecreateStorage(virDomainObj *vm, qemuMigrationCookieNBD *nbd, size_t nmigrate_disks, const char **migrate_disks, bool incremental) { int ret = -1; size_t i = 0; virConnectPtr conn = NULL; if (!nbd || !nbd->ndisks) return 0; for (i = 0; i < nbd->ndisks; i++) { virDomainDiskDef *disk; const char *diskSrcPath; g_autofree char *nvmePath = NULL; VIR_DEBUG("Looking up disk target '%s' (capacity=%llu)", nbd->disks[i].target, nbd->disks[i].capacity); if (!(disk = virDomainDiskByTarget(vm->def, nbd->disks[i].target))) { virReportError(VIR_ERR_INTERNAL_ERROR, _("unable to find disk by target: %1$s"), nbd->disks[i].target); goto cleanup; } if (disk->src->type == VIR_STORAGE_TYPE_NVME) { virPCIDeviceAddressGetSysfsFile(&disk->src->nvme->pciAddr, &nvmePath); diskSrcPath = nvmePath; } else { diskSrcPath = virDomainDiskGetSource(disk); } /* Skip disks we don't want to migrate and already existing disks. */ if (!qemuMigrationAnyCopyDisk(disk, nmigrate_disks, migrate_disks) || (diskSrcPath && virFileExists(diskSrcPath))) { continue; } if (incremental) { virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", _("pre-creation of storage targets for incremental " "storage migration is not supported")); goto cleanup; } VIR_DEBUG("Proceeding with disk source %s", NULLSTR(diskSrcPath)); if (qemuMigrationDstPrecreateDisk(&conn, disk, nbd->disks[i].capacity) < 0) goto cleanup; } ret = 0; cleanup: virObjectUnref(conn); return ret; } /** * qemuMigrationDstStartNBDServer: * @driver: qemu driver * @vm: domain * * Starts NBD server. This is a newer method to copy * storage during migration than using 'blk' and 'inc' * arguments in 'migrate' monitor command. * Error is reported here. * * Returns 0 on success, -1 otherwise. */ static int qemuMigrationDstStartNBDServer(virQEMUDriver *driver, virDomainObj *vm, const char *listenAddr, size_t nmigrate_disks, const char **migrate_disks, int nbdPort, const char *nbdURI, const char *tls_alias) { int ret = -1; qemuDomainObjPrivate *priv = vm->privateData; size_t i; virStorageNetHostDef server = { .name = (char *)listenAddr, /* cast away const */ .transport = VIR_STORAGE_NET_HOST_TRANS_TCP, .port = nbdPort, }; bool server_started = false; g_autoptr(virURI) uri = NULL; /* Prefer nbdURI */ if (nbdURI) { uri = virURIParse(nbdURI); if (!uri) return -1; if (!uri->scheme) { virReportError(VIR_ERR_INVALID_ARG, _("No URI scheme specified: %1$s"), nbdURI); return -1; } if (STREQ(uri->scheme, "tcp")) { server.transport = VIR_STORAGE_NET_HOST_TRANS_TCP; if (!uri->server || STREQ(uri->server, "")) { /* Since tcp://:/ is parsed as server = NULL and port = 0 * we should rather error out instead of auto-allocating a port * as that would be the exact opposite of what was requested. */ virReportError(VIR_ERR_INVALID_ARG, _("URI with tcp scheme did not provide a server part: %1$s"), nbdURI); return -1; } server.name = (char *)uri->server; if (uri->port) server.port = uri->port; } else if (STREQ(uri->scheme, "unix")) { if (!uri->path) { virReportError(VIR_ERR_INVALID_ARG, "%s", _("UNIX disks URI does not include path")); return -1; } server.transport = VIR_STORAGE_NET_HOST_TRANS_UNIX; server.socket = (char *)uri->path; } else { virReportError(VIR_ERR_INVALID_ARG, _("Unsupported scheme in disks URI: %1$s"), uri->scheme); return -1; } } else if (nbdPort < 0 || nbdPort > USHRT_MAX) { virReportError(VIR_ERR_INVALID_ARG, "%s", _("nbd port must be in range 0-65535")); return -1; } for (i = 0; i < vm->def->ndisks; i++) { virDomainDiskDef *disk = vm->def->disks[i]; g_autofree char *diskAlias = NULL; /* check whether disk should be migrated */ if (!qemuMigrationAnyCopyDisk(disk, nmigrate_disks, migrate_disks)) continue; if (disk->src->readonly || virStorageSourceIsEmpty(disk->src)) { virReportError(VIR_ERR_OPERATION_UNSUPPORTED, _("Cannot migrate empty or read-only disk %1$s"), disk->dst); goto cleanup; } if (!(diskAlias = qemuAliasDiskDriveFromDisk(disk))) goto cleanup; if (!server_started && server.transport == VIR_STORAGE_NET_HOST_TRANS_TCP) { if (server.port) { if (virPortAllocatorSetUsed(server.port) < 0) goto cleanup; } else { unsigned short port = 0; if (virPortAllocatorAcquire(driver->migrationPorts, &port) < 0) goto cleanup; server.port = port; } } if (qemuDomainObjEnterMonitorAsync(vm, VIR_ASYNC_JOB_MIGRATION_IN) < 0) goto cleanup; if (!server_started) { if (qemuMonitorNBDServerStart(priv->mon, &server, tls_alias) < 0) goto exit_monitor; server_started = true; } if (qemuBlockExportAddNBD(vm, disk->src, diskAlias, true, NULL) < 0) goto exit_monitor; qemuDomainObjExitMonitor(vm); } if (server.transport == VIR_STORAGE_NET_HOST_TRANS_TCP) priv->nbdPort = server.port; ret = 0; cleanup: if (ret < 0) virPortAllocatorRelease(server.port); return ret; exit_monitor: qemuDomainObjExitMonitor(vm); goto cleanup; } static int qemuMigrationDstStopNBDServer(virDomainObj *vm, qemuMigrationCookie *mig) { qemuDomainObjPrivate *priv = vm->privateData; if (!mig->nbd) return 0; if (qemuDomainObjEnterMonitorAsync(vm, VIR_ASYNC_JOB_MIGRATION_IN) < 0) return -1; if (qemuMonitorNBDServerStop(priv->mon) < 0) VIR_WARN("Unable to stop NBD server"); qemuDomainObjExitMonitor(vm); virPortAllocatorRelease(priv->nbdPort); priv->nbdPort = 0; return 0; } static void qemuMigrationNBDReportMirrorError(qemuBlockJobData *job, const char *diskdst) { if (job->errmsg) { virReportError(VIR_ERR_OPERATION_FAILED, _("migration of disk %1$s failed: %2$s"), diskdst, job->errmsg); } else { virReportError(VIR_ERR_OPERATION_FAILED, _("migration of disk %1$s failed"), diskdst); } } /** * qemuMigrationSrcNBDStorageCopyReady: * @vm: domain * * Check the status of all drives copied via qemuMigrationSrcNBDStorageCopy. * Any pending block job events for the mirrored disks will be processed. * * Returns 1 if all mirrors are "ready", * 0 if some mirrors are still performing initial sync, * -1 on error. */ static int qemuMigrationSrcNBDStorageCopyReady(virDomainObj *vm, virDomainAsyncJob asyncJob) { size_t i; size_t notReady = 0; for (i = 0; i < vm->def->ndisks; i++) { virDomainDiskDef *disk = vm->def->disks[i]; qemuDomainDiskPrivate *diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk); qemuBlockJobData *job; if (!diskPriv->migrating) continue; if (!(job = qemuBlockJobDiskGetJob(disk))) { virReportError(VIR_ERR_INTERNAL_ERROR, _("missing block job data for disk '%1$s'"), disk->dst); return -1; } qemuBlockJobUpdate(vm, job, asyncJob); if (job->state == VIR_DOMAIN_BLOCK_JOB_FAILED) { qemuMigrationNBDReportMirrorError(job, disk->dst); virObjectUnref(job); return -1; } if (job->state != VIR_DOMAIN_BLOCK_JOB_READY) notReady++; virObjectUnref(job); } 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; } } /* * If @abortMigration is false, the function will report an error and return a * different code in case a block job fails. This way we can properly abort * migration in case some block jobs failed once all memory has already been * transferred. * * Returns 1 if all mirrors are gone, * 0 if some mirrors are still active, * -1 some mirrors failed but some are still active, * -2 all mirrors are gone but some of them failed. */ static int qemuMigrationSrcNBDCopyCancelled(virDomainObj *vm, virDomainAsyncJob asyncJob, bool abortMigration) { size_t i; size_t active = 0; size_t completed = 0; bool failed = false; do { active = 0; completed = 0; for (i = 0; i < vm->def->ndisks; i++) { virDomainDiskDef *disk = vm->def->disks[i]; qemuDomainDiskPrivate *diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk); qemuBlockJobData *job; if (!diskPriv->migrating) continue; if (!(job = qemuBlockJobDiskGetJob(disk))) continue; qemuBlockJobUpdate(vm, job, asyncJob); switch (job->state) { case VIR_DOMAIN_BLOCK_JOB_FAILED: if (!abortMigration) { qemuMigrationNBDReportMirrorError(job, disk->dst); failed = true; } G_GNUC_FALLTHROUGH; case VIR_DOMAIN_BLOCK_JOB_CANCELED: case VIR_DOMAIN_BLOCK_JOB_COMPLETED: diskPriv->migrating = false; break; default: active++; } if (job->state == VIR_DOMAIN_BLOCK_JOB_COMPLETED) completed++; virObjectUnref(job); } /* Updating completed block job drops the lock thus we have to recheck * block jobs for disks that reside before the disk(s) with completed * block job. */ } while (completed > 0); if (failed) { if (active) { VIR_DEBUG("Some disk mirrors failed; still waiting for %zu " "disk mirrors to finish", active); return -1; } else { VIR_DEBUG("All disk mirrors are gone; some of them failed"); return -2; } } else { if (active) { VIR_DEBUG("Waiting for %zu disk mirrors to finish", active); return 0; } else { VIR_DEBUG("All disk mirrors are gone"); return 1; } } } /* * Returns 0 on success, * 1 when job is already completed or it failed and failNoJob is false, * -1 on error or when job failed and failNoJob is true. */ static int qemuMigrationSrcNBDCopyCancelOne(virDomainObj *vm, virDomainDiskDef *disk, qemuBlockJobData *job, bool abortMigration, virDomainAsyncJob asyncJob) { qemuDomainObjPrivate *priv = vm->privateData; int rv; qemuBlockJobUpdate(vm, job, asyncJob); switch (job->state) { case VIR_DOMAIN_BLOCK_JOB_FAILED: case VIR_DOMAIN_BLOCK_JOB_CANCELED: if (!abortMigration) { qemuMigrationNBDReportMirrorError(job, disk->dst); return -1; } G_GNUC_FALLTHROUGH; case VIR_DOMAIN_BLOCK_JOB_COMPLETED: return 1; } if (qemuDomainObjEnterMonitorAsync(vm, asyncJob) < 0) return -1; /* when we are aborting the migration we don't care about the data * consistency on the destination so that we can force cancel the mirror */ rv = qemuMonitorBlockJobCancel(priv->mon, job->name, abortMigration); qemuDomainObjExitMonitor(vm); if (rv < 0) return -1; return 0; } /** * qemuMigrationSrcNBDCopyCancel: * @driver: qemu driver * @vm: domain * @abortMigration: The migration is being cancelled. * * Cancel all drive-mirrors started by qemuMigrationSrcNBDStorageCopy. * Any pending block job events for the affected disks will be processed and * synchronous block job terminated regardless of return value unless qemu * has crashed. * * Returns 0 on success, -1 otherwise. */ static int qemuMigrationSrcNBDCopyCancel(virDomainObj *vm, bool abortMigration, virDomainAsyncJob asyncJob, virConnectPtr dconn) { virErrorPtr err = NULL; int ret = -1; size_t i; int rv; bool failed = false; VIR_DEBUG("Cancelling drive mirrors for domain %s", vm->def->name); for (i = 0; i < vm->def->ndisks; i++) { virDomainDiskDef *disk = vm->def->disks[i]; qemuDomainDiskPrivate *diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk); qemuBlockJobData *job; if (!(job = qemuBlockJobDiskGetJob(disk)) || !qemuBlockJobIsRunning(job)) diskPriv->migrating = false; if (!diskPriv->migrating) { virObjectUnref(job); continue; } rv = qemuMigrationSrcNBDCopyCancelOne(vm, disk, job, abortMigration, asyncJob); if (rv != 0) { if (rv < 0) { if (!err) virErrorPreserveLast(&err); failed = true; } qemuBlockJobSyncEnd(vm, job, asyncJob); diskPriv->migrating = false; } virObjectUnref(job); } while ((rv = qemuMigrationSrcNBDCopyCancelled(vm, asyncJob, abortMigration)) != 1) { if (!abortMigration && !failed && dconn && virConnectIsAlive(dconn) <= 0) { virReportError(VIR_ERR_OPERATION_FAILED, "%s", _("Lost connection to destination host")); failed = true; } if (rv < 0) { failed = true; if (rv == -2) break; } if (failed && !err) virErrorPreserveLast(&err); if (qemuDomainObjWait(vm) < 0) goto cleanup; } for (i = 0; i < vm->def->ndisks; i++) { virDomainDiskDef *disk = vm->def->disks[i]; qemuDomainDiskPrivate *diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk); if (!diskPriv->migrSource) continue; qemuBlockStorageSourceDetachOneBlockdev(vm, asyncJob, diskPriv->migrSource); g_clear_pointer(&diskPriv->migrSource, virObjectUnref); } ret = failed ? -1 : 0; cleanup: virErrorRestore(&err); return ret; } static int qemuMigrationSrcCancelRemoveTempBitmaps(virDomainObj *vm, virDomainAsyncJob asyncJob) { qemuDomainObjPrivate *priv = vm->privateData; qemuDomainJobPrivate *jobPriv = vm->job->privateData; GSList *next; for (next = jobPriv->migTempBitmaps; next; next = next->next) { qemuDomainJobPrivateMigrateTempBitmap *t = next->data; if (qemuDomainObjEnterMonitorAsync(vm, asyncJob) < 0) return -1; qemuMonitorBitmapRemove(priv->mon, t->nodename, t->bitmapname); qemuDomainObjExitMonitor(vm); } return 0; } static virStorageSource * qemuMigrationSrcNBDStorageCopyBlockdevPrepareSource(virDomainDiskDef *disk, const char *host, int port, const char *socket, const char *tlsAlias, const char *tlsHostname) { g_autoptr(virStorageSource) copysrc = NULL; copysrc = virStorageSourceNew(); copysrc->type = VIR_STORAGE_TYPE_NETWORK; copysrc->protocol = VIR_STORAGE_NET_PROTOCOL_NBD; copysrc->format = VIR_STORAGE_FILE_RAW; copysrc->backingStore = virStorageSourceNew(); if (!(copysrc->path = qemuAliasDiskDriveFromDisk(disk))) return NULL; copysrc->hosts = g_new0(virStorageNetHostDef, 1); copysrc->nhosts = 1; if (socket) { copysrc->hosts->transport = VIR_STORAGE_NET_HOST_TRANS_UNIX; copysrc->hosts->socket = g_strdup(socket); } else { copysrc->hosts->transport = VIR_STORAGE_NET_HOST_TRANS_TCP; copysrc->hosts->port = port; copysrc->hosts->name = g_strdup(host); } copysrc->tlsAlias = g_strdup(tlsAlias); copysrc->tlsHostname = g_strdup(tlsHostname); copysrc->nodestorage = g_strdup_printf("migration-%s-storage", disk->dst); copysrc->nodeformat = g_strdup_printf("migration-%s-format", disk->dst); return g_steal_pointer(©src); } static int qemuMigrationSrcNBDStorageCopyBlockdev(virDomainObj *vm, virDomainDiskDef *disk, const char *host, int port, const char *socket, unsigned long long mirror_speed, unsigned int mirror_shallow, const char *tlsAlias, const char *tlsHostname, bool syncWrites) { g_autoptr(qemuBlockStorageSourceAttachData) data = NULL; qemuDomainDiskPrivate *diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk); int mon_ret = 0; g_autoptr(virStorageSource) copysrc = NULL; g_autofree char *diskAlias = qemuAliasDiskDriveFromDisk(disk); if (!diskAlias) return -1; VIR_DEBUG("starting blockdev mirror for disk=%s to host=%s", disk->dst, host); if (!(copysrc = qemuMigrationSrcNBDStorageCopyBlockdevPrepareSource(disk, host, port, socket, tlsAlias, tlsHostname))) return -1; /* Migration via blockdev-mirror was supported sooner than the auto-read-only * feature was added to qemu */ if (!(data = qemuBlockStorageSourceAttachPrepareBlockdev(copysrc, copysrc->backingStore, false))) return -1; if (qemuDomainObjEnterMonitorAsync(vm, VIR_ASYNC_JOB_MIGRATION_OUT) < 0) return -1; mon_ret = qemuBlockStorageSourceAttachApply(qemuDomainGetMonitor(vm), data); if (mon_ret == 0) mon_ret = qemuMonitorBlockdevMirror(qemuDomainGetMonitor(vm), diskAlias, true, qemuDomainDiskGetTopNodename(disk), copysrc->nodeformat, mirror_speed, 0, 0, mirror_shallow, syncWrites); if (mon_ret != 0) qemuBlockStorageSourceAttachRollback(qemuDomainGetMonitor(vm), data); qemuDomainObjExitMonitor(vm); if (mon_ret < 0) return -1; diskPriv->migrSource = g_steal_pointer(©src); return 0; } static int qemuMigrationSrcNBDStorageCopyOne(virDomainObj *vm, virDomainDiskDef *disk, const char *host, int port, const char *socket, unsigned long long mirror_speed, bool mirror_shallow, const char *tlsAlias, const char *tlsHostname, unsigned int flags) { qemuDomainDiskPrivate *diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk); qemuBlockJobData *job = NULL; g_autofree char *diskAlias = NULL; bool syncWrites = !!(flags & VIR_MIGRATE_NON_SHARED_SYNCHRONOUS_WRITES); int rc; int ret = -1; if (!(diskAlias = qemuAliasDiskDriveFromDisk(disk))) return -1; if (!(job = qemuBlockJobDiskNew(vm, disk, QEMU_BLOCKJOB_TYPE_COPY, diskAlias))) return -1; qemuBlockJobSyncBegin(job); rc = qemuMigrationSrcNBDStorageCopyBlockdev(vm, disk, host, port, socket, mirror_speed, mirror_shallow, tlsAlias, tlsHostname, syncWrites); if (rc == 0) { diskPriv->migrating = true; qemuBlockJobStarted(job, vm); ret = 0; } qemuBlockJobStartupFinalize(vm, job); return ret; } /** * qemuMigrationSrcNBDStorageCopy: * @driver: qemu driver * @vm: domain * @mig: migration cookie * @host: where are we migrating to * @speed: bandwidth limit in MiB/s * * Migrate non-shared storage using the NBD protocol to the server running * inside the qemu process on dst and wait until the copy converges. * On failure, the caller is expected to call qemuMigrationSrcNBDCopyCancel * to stop all running copy operations. * * Returns 0 on success (@migrate_flags updated), * -1 otherwise. */ static int qemuMigrationSrcNBDStorageCopy(virQEMUDriver *driver, virDomainObj *vm, qemuMigrationCookie *mig, const char *host, unsigned long speed, size_t nmigrate_disks, const char **migrate_disks, virConnectPtr dconn, const char *tlsAlias, const char *tlsHostname, const char *nbdURI, unsigned int flags) { qemuDomainObjPrivate *priv = vm->privateData; int port; size_t i; unsigned long long mirror_speed = speed; bool mirror_shallow = flags & VIR_MIGRATE_NON_SHARED_INC; int rv; g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver); g_autoptr(virURI) uri = NULL; const char *socket = NULL; VIR_DEBUG("Starting drive mirrors for domain %s", vm->def->name); if (mirror_speed > LLONG_MAX >> 20) { virReportError(VIR_ERR_OVERFLOW, _("bandwidth must be less than %1$llu"), LLONG_MAX >> 20); return -1; } mirror_speed <<= 20; /* If qemu doesn't support overriding of TLS hostname for NBD connections * we won't attempt it */ if (!virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BLOCKDEV_NBD_TLS_HOSTNAME)) tlsHostname = NULL; /* steal NBD port and thus prevent its propagation back to destination */ port = mig->nbd->port; mig->nbd->port = 0; if (nbdURI) { uri = virURIParse(nbdURI); if (!uri) return -1; if (STREQ(uri->scheme, "tcp")) { if (uri->server && STRNEQ(uri->server, "")) host = (char *)uri->server; if (uri->port) port = uri->port; } else if (STREQ(uri->scheme, "unix")) { if (flags & VIR_MIGRATE_TLS) { virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", _("NBD migration with TLS is not supported over UNIX socket")); return -1; } if (!uri->path) { virReportError(VIR_ERR_INVALID_ARG, "%s", _("UNIX disks URI does not include path")); return -1; } socket = uri->path; if (qemuSecurityDomainSetPathLabel(driver, vm, socket, false) < 0) return -1; } else { virReportError(VIR_ERR_INVALID_ARG, _("Unsupported scheme in disks URI: %1$s"), uri->scheme); return -1; } } for (i = 0; i < vm->def->ndisks; i++) { virDomainDiskDef *disk = vm->def->disks[i]; /* check whether disk should be migrated */ if (!qemuMigrationAnyCopyDisk(disk, nmigrate_disks, migrate_disks)) continue; if (qemuMigrationSrcNBDStorageCopyOne(vm, disk, host, port, socket, mirror_speed, mirror_shallow, tlsAlias, tlsHostname, flags) < 0) return -1; if (virDomainObjSave(vm, driver->xmlopt, cfg->stateDir) < 0) { VIR_WARN("Failed to save status on vm %s", vm->def->name); return -1; } } while ((rv = qemuMigrationSrcNBDStorageCopyReady(vm, VIR_ASYNC_JOB_MIGRATION_OUT)) != 1) { if (rv < 0) return -1; if (vm->job->abortJob) { vm->job->current->status = VIR_DOMAIN_JOB_STATUS_CANCELED; virReportError(VIR_ERR_OPERATION_ABORTED, _("%1$s: %2$s"), virDomainAsyncJobTypeToString(vm->job->asyncJob), _("canceled by client")); return -1; } if (dconn && virConnectIsAlive(dconn) <= 0) { virReportError(VIR_ERR_OPERATION_FAILED, "%s", _("Lost connection to destination host")); return -1; } if (qemuDomainObjWait(vm) < 0) return -1; } qemuMigrationSrcFetchMirrorStats(vm, VIR_ASYNC_JOB_MIGRATION_OUT, vm->job->current); return 0; } /** * qemuMigrationSrcIsAllowedHostdev: * @def: domain definition * * Checks that @def does not contain any host devices unsupported across * migrations. Returns true if the vm is allowed to migrate. */ static bool qemuMigrationSrcIsAllowedHostdev(const virDomainDef *def) { size_t i; /* Migration with USB host devices is allowed, all other devices are * forbidden. */ for (i = 0; i < def->nhostdevs; i++) { virDomainHostdevDef *hostdev = def->hostdevs[i]; switch ((virDomainHostdevMode)hostdev->mode) { case VIR_DOMAIN_HOSTDEV_MODE_CAPABILITIES: virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", _("cannot migrate a domain with ")); return false; case VIR_DOMAIN_HOSTDEV_MODE_SUBSYS: switch ((virDomainHostdevSubsysType)hostdev->source.subsys.type) { case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB: /* USB devices can be "migrated" */ continue; case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_SCSI: case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_SCSI_HOST: case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_MDEV: virReportError(VIR_ERR_OPERATION_UNSUPPORTED, _("cannot migrate a domain with "), virDomainHostdevSubsysTypeToString(hostdev->source.subsys.type)); return false; case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI: /* * if the device has a * element, then migration *is* allowed because the * device will be auto-unplugged by QEMU during * migration. Generic and have their teaming configuration * stored in different places. */ if ((hostdev->teaming && hostdev->teaming->type == VIR_DOMAIN_NET_TEAMING_TYPE_TRANSIENT) || (hostdev->parentnet && hostdev->parentnet->teaming && hostdev->parentnet->teaming->type == VIR_DOMAIN_NET_TEAMING_TYPE_TRANSIENT)) { continue; } /* all other PCI hostdevs can't be migrated */ if (hostdev->parentnet) { virDomainNetType actualType = virDomainNetGetActualType(hostdev->parentnet); virReportError(VIR_ERR_OPERATION_UNSUPPORTED, _("cannot migrate a domain with "), virDomainNetTypeToString(actualType)); } else { virReportError(VIR_ERR_OPERATION_UNSUPPORTED, _("cannot migrate a domain with "), virDomainHostdevSubsysTypeToString(hostdev->source.subsys.type)); } return false; case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_LAST: virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("invalid hostdev subsystem type")); return false; } break; case VIR_DOMAIN_HOSTDEV_MODE_LAST: virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("invalid hostdev mode")); return false; } } return true; } static int qemuDomainGetMigrationBlockers(virDomainObj *vm, int asyncJob, char ***blockers) { qemuDomainObjPrivate *priv = vm->privateData; int rc; if (qemuDomainObjEnterMonitorAsync(vm, asyncJob) < 0) return -1; rc = qemuMonitorGetMigrationBlockers(priv->mon, blockers); qemuDomainObjExitMonitor(vm); return rc; } /** * qemuMigrationSrcIsAllowed: * @vm: domain object * @remote: migration is remote * @flags: migration flags (see struct virDomainMigrateFlags) * * Validates that the configuration of @vm can be migrated in various * situations. If @remote is true, the migration happens to remote host. @flags * is used to check various special migration types according to the request. * * Returns true if migration is supported. Reports libvirt error and returns * false otherwise. */ bool qemuMigrationSrcIsAllowed(virDomainObj *vm, bool remote, int asyncJob, unsigned int flags) { qemuDomainObjPrivate *priv = vm->privateData; int nsnapshots; int pauseReason; size_t i; /* perform these checks only when migrating to remote hosts */ if (remote) { nsnapshots = virDomainSnapshotObjListNum(vm->snapshots, NULL, 0); if (nsnapshots < 0) return false; if (nsnapshots > 0) { virReportError(VIR_ERR_OPERATION_INVALID, _("cannot migrate domain with %1$d snapshots"), nsnapshots); return false; } } /* following checks don't make sense for offline migration */ if (!(flags & VIR_MIGRATE_OFFLINE)) { bool blockedReasonsCap = virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_MIGRATION_BLOCKED_REASONS); /* Ask qemu if it has a migration blocker */ if (blockedReasonsCap) { g_auto(GStrv) blockers = NULL; if (qemuDomainGetMigrationBlockers(vm, asyncJob, &blockers) < 0) { return false; } if (blockers && blockers[0]) { g_autofree char *reasons = g_strjoinv("; ", blockers); virReportError(VIR_ERR_OPERATION_INVALID, _("cannot migrate domain: %1$s"), reasons); return false; } } else { /* checks here are for anything that doesn't need to be * checked by libvirt if running QEMU that can be queried * about migration blockers. */ if (!qemuMigrationSrcIsAllowedHostdev(vm->def)) return false; } if (remote) { /* cancel migration if disk I/O error is emitted while migrating */ if (flags & VIR_MIGRATE_ABORT_ON_ERROR && virDomainObjGetState(vm, &pauseReason) == VIR_DOMAIN_PAUSED && pauseReason == VIR_DOMAIN_PAUSED_IOERROR) { virReportError(VIR_ERR_OPERATION_INVALID, "%s", _("cannot migrate domain with I/O error")); return false; } if (virCloseCallbacksDomainIsRegistered(vm, NULL, qemuProcessAutoDestroy)) { virReportError(VIR_ERR_OPERATION_INVALID, "%s", _("domain is marked for auto destroy")); return false; } } if (qemuDomainHasBlockjob(vm, false)) { virReportError(VIR_ERR_OPERATION_INVALID, "%s", _("domain has active block job")); return false; } if (vm->def->cpu) { /* QEMU blocks migration and save with invariant TSC enabled * unless TSC frequency is explicitly set. */ if (virCPUCheckFeature(vm->def->os.arch, vm->def->cpu, "invtsc") == 1) { bool block = true; for (i = 0; i < vm->def->clock.ntimers; i++) { virDomainTimerDef *timer = vm->def->clock.timers[i]; if (timer->name == VIR_DOMAIN_TIMER_NAME_TSC && timer->frequency > 0) { block = false; break; } } if (block) { virReportError(VIR_ERR_OPERATION_INVALID, "%s", _("domain has 'invtsc' CPU feature but " "TSC frequency is not specified")); return false; } } } /* Verify that memory device config can be transferred reliably */ for (i = 0; i < vm->def->nmems; i++) { virDomainMemoryDef *mem = vm->def->mems[i]; if (mem->model == VIR_DOMAIN_MEMORY_MODEL_DIMM && mem->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_DIMM) { virReportError(VIR_ERR_OPERATION_INVALID, "%s", _("domain's dimm info lacks slot ID " "or base address")); return false; } } for (i = 0; i < vm->def->nshmems; i++) { virDomainShmemDef *shmem = vm->def->shmems[i]; if (shmem->model == VIR_DOMAIN_SHMEM_MODEL_IVSHMEM) { virReportError(VIR_ERR_OPERATION_INVALID, "%s", _("migration with legacy shmem device is not supported")); return false; } if (shmem->role != VIR_DOMAIN_SHMEM_ROLE_MASTER) { virReportError(VIR_ERR_OPERATION_INVALID, _("shmem device '%1$s' cannot be migrated, only shmem with role='%2$s' can be migrated"), shmem->name, virDomainShmemRoleTypeToString(VIR_DOMAIN_SHMEM_ROLE_MASTER)); return false; } } for (i = 0; i < vm->def->nnets; i++) { virDomainNetDef *net = vm->def->nets[i]; qemuSlirp *slirp; if (!blockedReasonsCap && net->type == VIR_DOMAIN_NET_TYPE_VDPA) { virReportError(VIR_ERR_OPERATION_INVALID, "%s", _("vDPA devices cannot be migrated")); return false; } slirp = QEMU_DOMAIN_NETWORK_PRIVATE(net)->slirp; if (slirp && !qemuSlirpHasFeature(slirp, QEMU_SLIRP_FEATURE_MIGRATE)) { virReportError(VIR_ERR_OPERATION_INVALID, "%s", _("a slirp-helper cannot be migrated")); return false; } } for (i = 0; i < vm->def->nfss; i++) { virDomainFSDef *fs = vm->def->fss[i]; if (fs->fsdriver == VIR_DOMAIN_FS_DRIVER_TYPE_VIRTIOFS) { virReportError(VIR_ERR_OPERATION_INVALID, "%s", _("migration with virtiofs device is not supported")); return false; } } if (priv->dbusVMStateIds && !virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_DBUS_VMSTATE)) { virReportError(VIR_ERR_OPERATION_INVALID, "%s", _("cannot migrate this domain without dbus-vmstate support")); return false; } for (i = 0; i < vm->def->ndisks; i++) { virDomainDiskDef *disk = vm->def->disks[i]; if (disk->transient) { virReportError(VIR_ERR_OPERATION_INVALID, "%s", _("migration with transient disk is not supported")); return false; } } if (qemuTPMHasSharedStorage(vm->def)&& !qemuTPMCanMigrateSharedStorage(vm->def)) { virReportError(VIR_ERR_NO_SUPPORT, "%s", _("the running swtpm does not support migration with shared storage")); return false; } } return true; } static bool qemuMigrationSrcIsSafe(virDomainDef *def, virQEMUCaps *qemuCaps, size_t nmigrate_disks, const char **migrate_disks, unsigned int flags) { bool storagemigration = flags & (VIR_MIGRATE_NON_SHARED_DISK | VIR_MIGRATE_NON_SHARED_INC); size_t i; int rc; for (i = 0; i < def->ndisks; i++) { virDomainDiskDef *disk = def->disks[i]; const char *src = virDomainDiskGetSource(disk); virStorageType actualType = virStorageSourceGetActualType(disk->src); bool unsafe = false; /* Disks without any source (i.e. floppies and CD-ROMs) * OR readonly are safe. */ if (virStorageSourceIsEmpty(disk->src) || disk->src->readonly) continue; /* Disks which are migrated by qemu are safe too. */ if (storagemigration && qemuMigrationAnyCopyDisk(disk, nmigrate_disks, migrate_disks)) continue; /* However, disks on local FS (e.g. ext4) are not safe. */ switch (actualType) { case VIR_STORAGE_TYPE_FILE: if ((rc = virFileIsSharedFS(src)) < 0) { return false; } else if (rc == 0) { unsafe = true; } if ((rc = virFileIsClusterFS(src)) < 0) return false; else if (rc == 1) continue; break; case VIR_STORAGE_TYPE_NETWORK: /* But network disks are safe again. */ continue; case VIR_STORAGE_TYPE_NVME: unsafe = true; break; case VIR_STORAGE_TYPE_VHOST_USER: case VIR_STORAGE_TYPE_NONE: case VIR_STORAGE_TYPE_BLOCK: case VIR_STORAGE_TYPE_DIR: case VIR_STORAGE_TYPE_VOLUME: case VIR_STORAGE_TYPE_LAST: break; } if (unsafe) { virReportError(VIR_ERR_MIGRATE_UNSAFE, "%s", _("Migration without shared storage is unsafe")); return false; } /* Our code elsewhere guarantees shared disks are either readonly (in * which case cache mode doesn't matter) or used with cache=none or used with cache=directsync */ if (disk->src->shared || disk->cachemode == VIR_DOMAIN_DISK_CACHE_DISABLE || disk->cachemode == VIR_DOMAIN_DISK_CACHE_DIRECTSYNC) continue; if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_MIGRATION_FILE_DROP_CACHE)) { VIR_DEBUG("QEMU supports flushing caches; migration is safe"); continue; } virReportError(VIR_ERR_MIGRATE_UNSAFE, "%s", _("Migration may lead to data corruption if disks" " use cache other than none or directsync")); return false; } return true; } void qemuMigrationSrcPostcopyFailed(virDomainObj *vm) { qemuDomainObjPrivate *priv = vm->privateData; virQEMUDriver *driver = priv->driver; virObjectEvent *event = NULL; virDomainState state; int reason; state = virDomainObjGetState(vm, &reason); VIR_DEBUG("%s/%s, asyncPaused=%u", virDomainStateTypeToString(state), virDomainStateReasonToString(state, reason), vm->job->asyncPaused); if (state != VIR_DOMAIN_PAUSED || virDomainObjIsFailedPostcopy(vm, vm->job)) return; VIR_WARN("Migration of domain %s failed during post-copy; " "leaving the domain paused", vm->def->name); vm->job->asyncPaused = true; virDomainObjSetState(vm, VIR_DOMAIN_PAUSED, VIR_DOMAIN_PAUSED_POSTCOPY_FAILED); event = virDomainEventLifecycleNewFromObj(vm, VIR_DOMAIN_EVENT_SUSPENDED, VIR_DOMAIN_EVENT_SUSPENDED_POSTCOPY_FAILED); virObjectEventStateQueue(driver->domainEventState, event); } void qemuMigrationDstPostcopyFailed(virDomainObj *vm) { qemuDomainObjPrivate *priv = vm->privateData; virQEMUDriver *driver = priv->driver; virObjectEvent *event = NULL; virDomainState state; int reason; state = virDomainObjGetState(vm, &reason); VIR_DEBUG("%s/%s, asyncPaused=%u", virDomainStateTypeToString(state), virDomainStateReasonToString(state, reason), vm->job->asyncPaused); if ((state != VIR_DOMAIN_RUNNING && state != VIR_DOMAIN_PAUSED) || virDomainObjIsFailedPostcopy(vm, vm->job)) return; VIR_WARN("Incoming migration of domain '%s' failed during post-copy; " "leaving the domain running", vm->def->name); vm->job->asyncPaused = true; if (state == VIR_DOMAIN_RUNNING) { virDomainObjSetState(vm, VIR_DOMAIN_RUNNING, VIR_DOMAIN_RUNNING_POSTCOPY_FAILED); event = virDomainEventLifecycleNewFromObj(vm, VIR_DOMAIN_EVENT_RESUMED, VIR_DOMAIN_EVENT_RESUMED_POSTCOPY_FAILED); } else { /* The domain was paused for other reasons (I/O error, ...) so we don't * want to rewrite the original reason and just emit a postcopy-failed * event. */ event = virDomainEventLifecycleNewFromObj(vm, VIR_DOMAIN_EVENT_SUSPENDED, VIR_DOMAIN_EVENT_SUSPENDED_POSTCOPY_FAILED); } virObjectEventStateQueue(driver->domainEventState, event); } static void qemuMigrationSrcWaitForSpice(virDomainObj *vm) { qemuDomainJobPrivate *jobPriv = vm->job->privateData; if (!jobPriv->spiceMigration) return; VIR_DEBUG("Waiting for SPICE to finish migration"); while (!jobPriv->spiceMigrated && !vm->job->abortJob) { if (qemuDomainObjWait(vm) < 0) return; } } static void qemuMigrationUpdateJobType(virDomainJobData *jobData) { qemuDomainJobDataPrivate *priv = jobData->privateData; switch ((qemuMonitorMigrationStatus) priv->stats.mig.status) { case QEMU_MONITOR_MIGRATION_STATUS_POSTCOPY: case QEMU_MONITOR_MIGRATION_STATUS_POSTCOPY_RECOVER: jobData->status = VIR_DOMAIN_JOB_STATUS_POSTCOPY; break; case QEMU_MONITOR_MIGRATION_STATUS_POSTCOPY_PAUSED: jobData->status = VIR_DOMAIN_JOB_STATUS_POSTCOPY_PAUSED; break; case QEMU_MONITOR_MIGRATION_STATUS_COMPLETED: jobData->status = VIR_DOMAIN_JOB_STATUS_HYPERVISOR_COMPLETED; break; case QEMU_MONITOR_MIGRATION_STATUS_INACTIVE: jobData->status = VIR_DOMAIN_JOB_STATUS_NONE; break; case QEMU_MONITOR_MIGRATION_STATUS_ERROR: jobData->status = VIR_DOMAIN_JOB_STATUS_FAILED; break; case QEMU_MONITOR_MIGRATION_STATUS_CANCELLED: jobData->status = VIR_DOMAIN_JOB_STATUS_CANCELED; break; case QEMU_MONITOR_MIGRATION_STATUS_PRE_SWITCHOVER: jobData->status = VIR_DOMAIN_JOB_STATUS_PAUSED; break; case QEMU_MONITOR_MIGRATION_STATUS_DEVICE: jobData->status = VIR_DOMAIN_JOB_STATUS_MIGRATING; break; case QEMU_MONITOR_MIGRATION_STATUS_SETUP: case QEMU_MONITOR_MIGRATION_STATUS_ACTIVE: case QEMU_MONITOR_MIGRATION_STATUS_CANCELLING: case QEMU_MONITOR_MIGRATION_STATUS_WAIT_UNPLUG: case QEMU_MONITOR_MIGRATION_STATUS_LAST: break; } } int qemuMigrationAnyFetchStats(virDomainObj *vm, virDomainAsyncJob asyncJob, virDomainJobData *jobData, char **error) { qemuDomainObjPrivate *priv = vm->privateData; qemuMonitorMigrationStats stats; qemuDomainJobDataPrivate *privJob = jobData->privateData; int rv; if (qemuDomainObjEnterMonitorAsync(vm, asyncJob) < 0) return -1; rv = qemuMonitorGetMigrationStats(priv->mon, &stats, error); qemuDomainObjExitMonitor(vm); if (rv < 0) return -1; privJob->stats.mig = stats; return 0; } static const char * qemuMigrationJobName(virDomainObj *vm) { switch (vm->job->asyncJob) { case VIR_ASYNC_JOB_MIGRATION_OUT: return _("migration out"); case VIR_ASYNC_JOB_SAVE: return _("domain save"); case VIR_ASYNC_JOB_DUMP: return _("domain core dump"); case VIR_ASYNC_JOB_MIGRATION_IN: return _("migration in"); case VIR_ASYNC_JOB_SNAPSHOT: return _("snapshot"); case VIR_ASYNC_JOB_START: return _("start"); case VIR_ASYNC_JOB_BACKUP: return _("backup"); case VIR_ASYNC_JOB_NONE: case VIR_ASYNC_JOB_LAST: default: return _("undefined"); } } static int qemuMigrationJobCheckStatus(virDomainObj *vm, virDomainAsyncJob asyncJob) { virDomainJobData *jobData = vm->job->current; qemuDomainJobDataPrivate *privJob = jobData->privateData; g_autofree char *error = NULL; if (privJob->stats.mig.status == QEMU_MONITOR_MIGRATION_STATUS_ERROR) { if (qemuMigrationAnyFetchStats(vm, asyncJob, jobData, &error) < 0) return -1; } qemuMigrationUpdateJobType(jobData); switch (jobData->status) { case VIR_DOMAIN_JOB_STATUS_NONE: virReportError(VIR_ERR_OPERATION_FAILED, _("job '%1$s' is not active"), qemuMigrationJobName(vm)); return -1; case VIR_DOMAIN_JOB_STATUS_FAILED: if (error) { virReportError(VIR_ERR_OPERATION_FAILED, _("job '%1$s' failed: %2$s"), qemuMigrationJobName(vm), error); } else { virReportError(VIR_ERR_OPERATION_FAILED, _("job '%1$s' unexpectedly failed"), qemuMigrationJobName(vm)); } return -1; case VIR_DOMAIN_JOB_STATUS_CANCELED: virReportError(VIR_ERR_OPERATION_ABORTED, _("job '%1$s' canceled by client"), qemuMigrationJobName(vm)); return -1; case VIR_DOMAIN_JOB_STATUS_POSTCOPY_PAUSED: virReportError(VIR_ERR_OPERATION_FAILED, _("job '%1$s' failed in post-copy phase"), qemuMigrationJobName(vm)); return -1; case VIR_DOMAIN_JOB_STATUS_COMPLETED: case VIR_DOMAIN_JOB_STATUS_ACTIVE: case VIR_DOMAIN_JOB_STATUS_MIGRATING: case VIR_DOMAIN_JOB_STATUS_HYPERVISOR_COMPLETED: case VIR_DOMAIN_JOB_STATUS_POSTCOPY: case VIR_DOMAIN_JOB_STATUS_PAUSED: break; } return 0; } enum qemuMigrationCompletedFlags { QEMU_MIGRATION_COMPLETED_ABORT_ON_ERROR = (1 << 0), /* This flag should only be set when run on src host */ QEMU_MIGRATION_COMPLETED_CHECK_STORAGE = (1 << 1), QEMU_MIGRATION_COMPLETED_POSTCOPY = (1 << 2), QEMU_MIGRATION_COMPLETED_PRE_SWITCHOVER = (1 << 3), }; /** * Returns 1 if migration completed successfully, * 0 if the domain is still being migrated, * -1 migration failed, * -2 something else failed, we need to cancel migration. */ static int qemuMigrationAnyCompleted(virDomainObj *vm, virDomainAsyncJob asyncJob, virConnectPtr dconn, unsigned int flags) { virDomainJobData *jobData = vm->job->current; int pauseReason; if (qemuMigrationJobCheckStatus(vm, asyncJob) < 0) goto error; /* This flag should only be set when run on src host */ if (flags & QEMU_MIGRATION_COMPLETED_CHECK_STORAGE && qemuMigrationSrcNBDStorageCopyReady(vm, asyncJob) < 0) goto error; if (flags & QEMU_MIGRATION_COMPLETED_ABORT_ON_ERROR && virDomainObjGetState(vm, &pauseReason) == VIR_DOMAIN_PAUSED && pauseReason == VIR_DOMAIN_PAUSED_IOERROR) { virReportError(VIR_ERR_OPERATION_FAILED, _("job '%1$s' failed due to I/O error"), qemuMigrationJobName(vm)); goto error; } if (dconn && virConnectIsAlive(dconn) <= 0) { virReportError(VIR_ERR_OPERATION_FAILED, "%s", _("Lost connection to destination host")); goto error; } /* Migration was paused before serializing device state, let's return to * the caller so that it can finish all block jobs, resume migration, and * wait again for the real end of the migration. */ if (flags & QEMU_MIGRATION_COMPLETED_PRE_SWITCHOVER && jobData->status == VIR_DOMAIN_JOB_STATUS_PAUSED) { VIR_DEBUG("Migration paused before switchover"); return 1; } /* In case of postcopy the source considers migration completed at the * moment it switched from active to postcopy-active state. The destination * will continue waiting until the migrate state changes to completed. */ if (flags & QEMU_MIGRATION_COMPLETED_POSTCOPY && jobData->status == VIR_DOMAIN_JOB_STATUS_POSTCOPY) { VIR_DEBUG("Migration switched to post-copy"); return 1; } if (jobData->status == VIR_DOMAIN_JOB_STATUS_HYPERVISOR_COMPLETED) return 1; else return 0; error: switch (jobData->status) { case VIR_DOMAIN_JOB_STATUS_MIGRATING: case VIR_DOMAIN_JOB_STATUS_POSTCOPY: case VIR_DOMAIN_JOB_STATUS_PAUSED: /* The migration was aborted by us rather than QEMU itself. */ jobData->status = VIR_DOMAIN_JOB_STATUS_FAILED; return -2; case VIR_DOMAIN_JOB_STATUS_HYPERVISOR_COMPLETED: /* Something failed after QEMU already finished the migration. */ jobData->status = VIR_DOMAIN_JOB_STATUS_FAILED; return -1; case VIR_DOMAIN_JOB_STATUS_FAILED: case VIR_DOMAIN_JOB_STATUS_CANCELED: case VIR_DOMAIN_JOB_STATUS_POSTCOPY_PAUSED: /* QEMU aborted the migration. */ return -1; case VIR_DOMAIN_JOB_STATUS_ACTIVE: case VIR_DOMAIN_JOB_STATUS_COMPLETED: case VIR_DOMAIN_JOB_STATUS_NONE: /* Impossible. */ break; } return -1; } /* Returns 0 on success, -2 when migration needs to be cancelled, or -1 when * QEMU reports failed migration. */ static int qemuMigrationSrcWaitForCompletion(virDomainObj *vm, virDomainAsyncJob asyncJob, virConnectPtr dconn, unsigned int flags) { qemuDomainObjPrivate *priv = vm->privateData; virDomainJobData *jobData = vm->job->current; int rv; jobData->status = VIR_DOMAIN_JOB_STATUS_MIGRATING; while ((rv = qemuMigrationAnyCompleted(vm, asyncJob, dconn, flags)) != 1) { if (rv < 0) return rv; if (qemuDomainObjWait(vm) < 0) { if (virDomainObjIsActive(vm) && !priv->beingDestroyed) jobData->status = VIR_DOMAIN_JOB_STATUS_FAILED; return -2; } } ignore_value(qemuMigrationAnyFetchStats(vm, asyncJob, jobData, NULL)); qemuDomainJobDataUpdateTime(jobData); qemuDomainJobDataUpdateDowntime(jobData); g_clear_pointer(&vm->job->completed, virDomainJobDataFree); vm->job->completed = virDomainJobDataCopy(jobData); vm->job->completed->status = VIR_DOMAIN_JOB_STATUS_COMPLETED; if (asyncJob != VIR_ASYNC_JOB_MIGRATION_OUT && jobData->status == VIR_DOMAIN_JOB_STATUS_HYPERVISOR_COMPLETED) jobData->status = VIR_DOMAIN_JOB_STATUS_COMPLETED; return 0; } static int qemuMigrationDstWaitForCompletion(virDomainObj *vm, virDomainAsyncJob asyncJob, bool postcopy) { unsigned int flags = 0; int rv; VIR_DEBUG("Waiting for incoming migration to complete"); if (postcopy) flags = QEMU_MIGRATION_COMPLETED_POSTCOPY; while ((rv = qemuMigrationAnyCompleted(vm, asyncJob, NULL, flags)) != 1) { if (rv < 0 || qemuDomainObjWait(vm) < 0) return -1; } return 0; } static int qemuMigrationSrcGraphicsRelocate(virDomainObj *vm, qemuMigrationCookie *cookie, const char *graphicsuri) { qemuDomainObjPrivate *priv = vm->privateData; const char *listenAddress = NULL; virSocketAddr addr; g_autoptr(virURI) uri = NULL; int type = -1; int port = -1; int tlsPort = -1; const char *tlsSubject = NULL; int rc = -1; if (!cookie || (!cookie->graphics && !graphicsuri)) return 0; if (graphicsuri && !(uri = virURIParse(graphicsuri))) return -1; if (cookie->graphics) { type = cookie->graphics->type; listenAddress = cookie->graphics->listen; if (!listenAddress || (virSocketAddrParse(&addr, listenAddress, AF_UNSPEC) > 0 && virSocketAddrIsWildcard(&addr))) listenAddress = cookie->remoteHostname; port = cookie->graphics->port; tlsPort = cookie->graphics->tlsPort; tlsSubject = cookie->graphics->tlsSubject; } if (uri) { size_t i; if ((type = virDomainGraphicsTypeFromString(uri->scheme)) < 0) { virReportError(VIR_ERR_INVALID_ARG, _("unknown graphics type %1$s"), uri->scheme); return -1; } if (uri->server) listenAddress = uri->server; if (uri->port > 0) port = uri->port; for (i = 0; i < uri->paramsCount; i++) { virURIParam *param = uri->params + i; if (STRCASEEQ(param->name, "tlsPort")) { if (virStrToLong_i(param->value, NULL, 10, &tlsPort) < 0) { virReportError(VIR_ERR_INVALID_ARG, _("invalid tlsPort number: %1$s"), param->value); return -1; } } else if (STRCASEEQ(param->name, "tlsSubject")) { tlsSubject = param->value; } } } /* QEMU doesn't support VNC relocation yet, so * skip it to avoid generating an error */ if (type != VIR_DOMAIN_GRAPHICS_TYPE_SPICE) return 0; /* Older libvirt sends port == 0 for listen type='none' graphics. It's * safe to ignore such requests since relocation to unknown port does * not make sense in general. */ if (port <= 0 && tlsPort <= 0) return 0; if (qemuDomainObjEnterMonitorAsync(vm, VIR_ASYNC_JOB_MIGRATION_OUT) == 0) { qemuDomainJobPrivate *jobPriv = vm->job->privateData; rc = qemuMonitorGraphicsRelocate(priv->mon, type, listenAddress, port, tlsPort, tlsSubject); jobPriv->spiceMigration = !rc; qemuDomainObjExitMonitor(vm); } return rc; } static int qemuMigrationDstOPDRelocate(virQEMUDriver *driver G_GNUC_UNUSED, virDomainObj *vm, qemuMigrationCookie *cookie) { virDomainNetDef *netptr; size_t i; for (i = 0; i < cookie->network->nnets; i++) { netptr = vm->def->nets[i]; switch (cookie->network->net[i].vporttype) { case VIR_NETDEV_VPORT_PROFILE_NONE: case VIR_NETDEV_VPORT_PROFILE_8021QBG: case VIR_NETDEV_VPORT_PROFILE_8021QBH: break; case VIR_NETDEV_VPORT_PROFILE_OPENVSWITCH: if (virNetDevOpenvswitchSetMigrateData(cookie->network->net[i].portdata, netptr->ifname) != 0) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Unable to run command to set OVS port data for interface %1$s"), netptr->ifname); return -1; } break; default: break; } } return 0; } int qemuMigrationDstCheckProtocol(virQEMUCaps *qemuCaps, const char *migrateFrom) { if (STRPREFIX(migrateFrom, "rdma")) { if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_MIGRATE_RDMA)) { virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", _("incoming RDMA migration is not supported " "with this QEMU binary")); return -1; } } else if (!STRPREFIX(migrateFrom, "tcp") && !STRPREFIX(migrateFrom, "exec") && !STRPREFIX(migrateFrom, "fd") && !STRPREFIX(migrateFrom, "unix") && STRNEQ(migrateFrom, "stdio")) { virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", _("unknown migration protocol")); return -1; } return 0; } char * qemuMigrationDstGetURI(const char *migrateFrom, int migrateFd) { char *uri = NULL; if (STREQ(migrateFrom, "stdio")) uri = g_strdup_printf("fd:%d", migrateFd); else uri = g_strdup(migrateFrom); return uri; } int qemuMigrationDstRun(virDomainObj *vm, const char *uri, virDomainAsyncJob asyncJob) { qemuDomainObjPrivate *priv = vm->privateData; int rv; VIR_DEBUG("Setting up incoming migration with URI %s", uri); if (qemuDomainObjEnterMonitorAsync(vm, asyncJob) < 0) return -1; rv = qemuMonitorSetDBusVMStateIdList(priv->mon, priv->dbusVMStateIds); if (rv < 0) goto exit_monitor; rv = qemuMonitorMigrateIncoming(priv->mon, uri); exit_monitor: qemuDomainObjExitMonitor(vm); if (rv < 0) return -1; if (asyncJob == VIR_ASYNC_JOB_MIGRATION_IN) { /* qemuMigrationDstWaitForCompletion is called from the Finish phase */ return 0; } if (qemuMigrationDstWaitForCompletion(vm, asyncJob, false) < 0) return -1; return 0; } /* This is called for outgoing non-p2p and incoming migrations when a * connection to the client which controls the migration was closed but we * were waiting for it to follow up with the next phase, that is, in between * qemuDomainMigrateBegin3 and qemuDomainMigratePerform3, * qemuDomainMigratePerform3 and qemuDomainMigrateConfirm3, or * qemuDomainMigratePrepare3 and qemuDomainMigrateFinish3. */ static void qemuMigrationAnyConnectionClosed(virDomainObj *vm, virConnectPtr conn) { qemuDomainJobPrivate *jobPriv = vm->job->privateData; bool postcopy = false; int phase; VIR_DEBUG("vm=%s, conn=%p, asyncJob=%s, phase=%s", vm->def->name, conn, virDomainAsyncJobTypeToString(vm->job->asyncJob), qemuDomainAsyncJobPhaseToString(vm->job->asyncJob, vm->job->phase)); if (!qemuMigrationJobIsActive(vm, VIR_ASYNC_JOB_MIGRATION_IN) && !qemuMigrationJobIsActive(vm, VIR_ASYNC_JOB_MIGRATION_OUT)) return; VIR_WARN("The connection which controls migration of domain %s was closed", vm->def->name); switch ((qemuMigrationJobPhase) vm->job->phase) { case QEMU_MIGRATION_PHASE_BEGIN3: VIR_DEBUG("Aborting outgoing migration after Begin phase"); break; case QEMU_MIGRATION_PHASE_PERFORM3_DONE: if (virDomainObjIsPostcopy(vm, vm->job)) { VIR_DEBUG("Migration protocol interrupted in post-copy mode"); postcopy = true; } else { VIR_WARN("Migration of domain %s finished but we don't know if the " "domain was successfully started on destination or not", vm->def->name); } break; case QEMU_MIGRATION_PHASE_POSTCOPY_FAILED: case QEMU_MIGRATION_PHASE_BEGIN_RESUME: case QEMU_MIGRATION_PHASE_PERFORM_RESUME: case QEMU_MIGRATION_PHASE_PREPARE_RESUME: VIR_DEBUG("Connection closed while resuming failed post-copy migration"); postcopy = true; break; case QEMU_MIGRATION_PHASE_PREPARE: /* incoming migration; the domain will be autodestroyed */ return; case QEMU_MIGRATION_PHASE_PERFORM3: /* cannot be seen without an active migration API; unreachable */ case QEMU_MIGRATION_PHASE_CONFIRM3: case QEMU_MIGRATION_PHASE_CONFIRM3_CANCELLED: case QEMU_MIGRATION_PHASE_CONFIRM_RESUME: case QEMU_MIGRATION_PHASE_FINISH2: case QEMU_MIGRATION_PHASE_FINISH3: case QEMU_MIGRATION_PHASE_FINISH_RESUME: /* all done; unreachable */ case QEMU_MIGRATION_PHASE_PERFORM2: /* single phase outgoing migration; unreachable */ case QEMU_MIGRATION_PHASE_NONE: case QEMU_MIGRATION_PHASE_LAST: /* unreachable */ return; } if (postcopy) phase = QEMU_MIGRATION_PHASE_POSTCOPY_FAILED; else phase = QEMU_MIGRATION_PHASE_CONFIRM3_CANCELLED; ignore_value(qemuMigrationJobStartPhase(vm, phase)); if (postcopy) { if (vm->job->asyncJob == VIR_ASYNC_JOB_MIGRATION_OUT) qemuMigrationSrcPostcopyFailed(vm); else qemuMigrationDstPostcopyFailed(vm); qemuMigrationJobContinue(vm, qemuProcessCleanupMigrationJob); } else { qemuMigrationParamsReset(vm, vm->job->asyncJob, jobPriv->migParams, vm->job->apiFlags); qemuMigrationJobFinish(vm); } } /** * qemuMigrationSrcBeginPhaseBlockDirtyBitmaps: * @mig: migration cookie struct * @vm: domain object * @migrate_disks: disks which are being migrated * @nmigrage_disks: number of @migrate_disks * * Enumerates block dirty bitmaps on disks which will undergo storage migration * and fills them into @mig to be offered to the destination. */ static int qemuMigrationSrcBeginPhaseBlockDirtyBitmaps(qemuMigrationCookie *mig, virDomainObj *vm, const char **migrate_disks, size_t nmigrate_disks) { GSList *disks = NULL; size_t i; g_autoptr(GHashTable) blockNamedNodeData = NULL; if (!(blockNamedNodeData = qemuBlockGetNamedNodeData(vm, vm->job->asyncJob))) return -1; for (i = 0; i < vm->def->ndisks; i++) { qemuMigrationBlockDirtyBitmapsDisk *disk; GSList *bitmaps = NULL; virDomainDiskDef *diskdef = vm->def->disks[i]; qemuBlockNamedNodeData *nodedata = virHashLookup(blockNamedNodeData, diskdef->src->nodeformat); size_t j; if (!nodedata) continue; if (migrate_disks) { bool migrating = false; for (j = 0; j < nmigrate_disks; j++) { if (STREQ(migrate_disks[j], diskdef->dst)) { migrating = true; break; } } if (!migrating) continue; } for (j = 0; j < nodedata->nbitmaps; j++) { qemuMigrationBlockDirtyBitmapsDiskBitmap *bitmap; if (!qemuBlockBitmapChainIsValid(diskdef->src, nodedata->bitmaps[j]->name, blockNamedNodeData)) continue; bitmap = g_new0(qemuMigrationBlockDirtyBitmapsDiskBitmap, 1); bitmap->bitmapname = g_strdup(nodedata->bitmaps[j]->name); bitmap->alias = g_strdup_printf("libvirt-%s-%s", diskdef->dst, nodedata->bitmaps[j]->name); bitmaps = g_slist_prepend(bitmaps, bitmap); } if (!bitmaps) continue; disk = g_new0(qemuMigrationBlockDirtyBitmapsDisk, 1); disk->target = g_strdup(diskdef->dst); disk->bitmaps = bitmaps; disks = g_slist_prepend(disks, disk); } if (!disks) return 0; mig->blockDirtyBitmaps = disks; mig->flags |= QEMU_MIGRATION_COOKIE_BLOCK_DIRTY_BITMAPS; return 0; } int qemuMigrationAnyRefreshStatus(virDomainObj *vm, virDomainAsyncJob asyncJob, virDomainJobStatus *status) { g_autoptr(virDomainJobData) jobData = NULL; qemuDomainJobDataPrivate *priv; jobData = virDomainJobDataInit(&virQEMUDriverDomainJobConfig.jobDataPrivateCb); priv = jobData->privateData; if (qemuMigrationAnyFetchStats(vm, asyncJob, jobData, NULL) < 0) return -1; qemuMigrationUpdateJobType(jobData); VIR_DEBUG("QEMU reports domain '%s' is in '%s' migration state, translated as %d", vm->def->name, qemuMonitorMigrationStatusTypeToString(priv->stats.mig.status), jobData->status); *status = jobData->status; return 0; } static char * qemuMigrationSrcBeginXML(virDomainObj *vm, const char *xmlin, char **cookieout, int *cookieoutlen, unsigned int cookieFlags, const char **migrate_disks, size_t nmigrate_disks, unsigned int flags) { qemuDomainObjPrivate *priv = vm->privateData; virQEMUDriver *driver = priv->driver; g_autoptr(qemuMigrationCookie) mig = NULL; if (priv->origCPU) cookieFlags |= QEMU_MIGRATION_COOKIE_CPU; if (!(flags & VIR_MIGRATE_OFFLINE)) cookieFlags |= QEMU_MIGRATION_COOKIE_CAPS; if (!(mig = qemuMigrationCookieNew(vm->def, priv->origname))) return NULL; if (cookieFlags & QEMU_MIGRATION_COOKIE_NBD && virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_MIGRATION_PARAM_BLOCK_BITMAP_MAPPING) && qemuMigrationSrcBeginPhaseBlockDirtyBitmaps(mig, vm, migrate_disks, nmigrate_disks) < 0) return NULL; if (qemuMigrationCookieFormat(mig, driver, vm, QEMU_MIGRATION_SOURCE, cookieout, cookieoutlen, cookieFlags) < 0) return NULL; if (xmlin) { g_autoptr(virDomainDef) def = NULL; if (!(def = virDomainDefParseString(xmlin, driver->xmlopt, priv->qemuCaps, VIR_DOMAIN_DEF_PARSE_INACTIVE | VIR_DOMAIN_DEF_PARSE_SKIP_VALIDATE))) return NULL; if (!qemuDomainCheckABIStability(driver, vm, def)) return NULL; return qemuDomainDefFormatLive(driver, priv->qemuCaps, def, NULL, false, true); } return qemuDomainDefFormatLive(driver, priv->qemuCaps, vm->def, priv->origCPU, false, true); } /* The caller is supposed to lock the vm and start a migration job. */ static char * qemuMigrationSrcBeginPhase(virQEMUDriver *driver, virDomainObj *vm, const char *xmlin, const char *dname, char **cookieout, int *cookieoutlen, size_t nmigrate_disks, const char **migrate_disks, unsigned int flags) { qemuDomainObjPrivate *priv = vm->privateData; unsigned int cookieFlags = QEMU_MIGRATION_COOKIE_LOCKSTATE; VIR_DEBUG("driver=%p, vm=%p, xmlin=%s, dname=%s," " cookieout=%p, cookieoutlen=%p," " nmigrate_disks=%zu, migrate_disks=%p, flags=0x%x", driver, vm, NULLSTR(xmlin), NULLSTR(dname), cookieout, cookieoutlen, nmigrate_disks, migrate_disks, flags); /* Only set the phase if we are inside VIR_ASYNC_JOB_MIGRATION_OUT. * Otherwise we will start the async job later in the perform phase losing * change protection. */ if (vm->job->asyncJob == VIR_ASYNC_JOB_MIGRATION_OUT && qemuMigrationJobStartPhase(vm, QEMU_MIGRATION_PHASE_BEGIN3) < 0) return NULL; if (!qemuMigrationSrcIsAllowed(vm, true, vm->job->asyncJob, flags)) return NULL; if (!(flags & (VIR_MIGRATE_UNSAFE | VIR_MIGRATE_OFFLINE)) && !qemuMigrationSrcIsSafe(vm->def, priv->qemuCaps, nmigrate_disks, migrate_disks, flags)) return NULL; if (flags & VIR_MIGRATE_POSTCOPY && (!(flags & VIR_MIGRATE_LIVE) || flags & VIR_MIGRATE_PAUSED)) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("post-copy migration is not supported with non-live " "or paused migration")); return NULL; } if (flags & VIR_MIGRATE_POSTCOPY && flags & VIR_MIGRATE_TUNNELLED) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("post-copy is not supported with tunnelled migration")); return NULL; } if (flags & VIR_MIGRATE_OFFLINE) { if (flags & (VIR_MIGRATE_NON_SHARED_DISK | VIR_MIGRATE_NON_SHARED_INC)) { virReportError(VIR_ERR_OPERATION_INVALID, "%s", _("offline migration cannot handle non-shared storage")); return NULL; } if (!(flags & VIR_MIGRATE_PERSIST_DEST)) { virReportError(VIR_ERR_OPERATION_INVALID, "%s", _("offline migration must be specified with the persistent flag set")); return NULL; } if (flags & VIR_MIGRATE_TUNNELLED) { virReportError(VIR_ERR_OPERATION_INVALID, "%s", _("tunnelled offline migration does not make sense")); return NULL; } } if (flags & VIR_MIGRATE_ZEROCOPY && !(flags & VIR_MIGRATE_PARALLEL)) { virReportError(VIR_ERR_OPERATION_INVALID, "%s", _("zero-copy is only available for parallel migration")); return NULL; } if (flags & (VIR_MIGRATE_NON_SHARED_DISK | VIR_MIGRATE_NON_SHARED_INC)) { if (flags & VIR_MIGRATE_TUNNELLED) { virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", _("migration of non-shared storage is not supported with tunnelled migration and this QEMU")); } if (nmigrate_disks) { size_t i, j; /* Check user requested only known disk targets. */ for (i = 0; i < nmigrate_disks; i++) { for (j = 0; j < vm->def->ndisks; j++) { if (STREQ(vm->def->disks[j]->dst, migrate_disks[i])) break; } if (j == vm->def->ndisks) { virReportError(VIR_ERR_INVALID_ARG, _("disk target %1$s not found"), migrate_disks[i]); return NULL; } } } priv->nbdPort = 0; if (qemuMigrationHasAnyStorageMigrationDisks(vm->def, migrate_disks, nmigrate_disks)) cookieFlags |= QEMU_MIGRATION_COOKIE_NBD; } if (virDomainDefHasMemoryHotplug(vm->def) || ((flags & VIR_MIGRATE_PERSIST_DEST) && vm->newDef && virDomainDefHasMemoryHotplug(vm->newDef))) cookieFlags |= QEMU_MIGRATION_COOKIE_MEMORY_HOTPLUG; if (!qemuDomainVcpuHotplugIsInOrder(vm->def) || ((flags & VIR_MIGRATE_PERSIST_DEST) && vm->newDef && !qemuDomainVcpuHotplugIsInOrder(vm->newDef))) cookieFlags |= QEMU_MIGRATION_COOKIE_CPU_HOTPLUG; return qemuMigrationSrcBeginXML(vm, xmlin, cookieout, cookieoutlen, cookieFlags, migrate_disks, nmigrate_disks, flags); } static bool qemuMigrationAnyCanResume(virDomainObj *vm, virDomainAsyncJob job, unsigned int flags, qemuMigrationJobPhase expectedPhase) { VIR_DEBUG("vm=%p, job=%s, flags=0x%x, expectedPhase=%s", vm, virDomainAsyncJobTypeToString(job), flags, qemuDomainAsyncJobPhaseToString(VIR_ASYNC_JOB_MIGRATION_OUT, expectedPhase)); if (!(flags & VIR_MIGRATE_POSTCOPY)) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("resuming failed post-copy migration requires post-copy to be enabled")); return false; } /* This should never happen since POSTCOPY_RESUME is newer than * CHANGE_PROTECTION, but let's check it anyway in case we're talking to * a weird client. */ if (job == VIR_ASYNC_JOB_MIGRATION_OUT && expectedPhase < QEMU_MIGRATION_PHASE_PERFORM_RESUME && !(flags & (VIR_MIGRATE_CHANGE_PROTECTION | VIR_MIGRATE_PEER2PEER))) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("resuming failed post-copy migration requires change protection")); return false; } if (!qemuMigrationJobIsActive(vm, job)) return false; if (vm->job->asyncOwner != 0 && vm->job->asyncOwner != virThreadSelfID()) { virReportError(VIR_ERR_OPERATION_INVALID, _("migration of domain %1$s is being actively monitored by another thread"), vm->def->name); return false; } if (!virDomainObjIsPostcopy(vm, vm->job)) { virReportError(VIR_ERR_OPERATION_INVALID, _("migration of domain %1$s is not in post-copy phase"), vm->def->name); return false; } if (vm->job->phase < QEMU_MIGRATION_PHASE_POSTCOPY_FAILED && !virDomainObjIsFailedPostcopy(vm, vm->job)) { virReportError(VIR_ERR_OPERATION_INVALID, _("post-copy migration of domain %1$s has not failed"), vm->def->name); return false; } if (vm->job->phase > expectedPhase) { virReportError(VIR_ERR_OPERATION_INVALID, _("resuming failed post-copy migration of domain %1$s already in progress"), vm->def->name); return false; } return true; } static char * qemuMigrationSrcBeginResume(virDomainObj *vm, const char *xmlin, char **cookieout, int *cookieoutlen, unsigned int flags) { virDomainJobStatus status; if (qemuMigrationAnyRefreshStatus(vm, VIR_ASYNC_JOB_MIGRATION_OUT, &status) < 0) return NULL; if (status != VIR_DOMAIN_JOB_STATUS_POSTCOPY_PAUSED) { virReportError(VIR_ERR_OPERATION_INVALID, "%s", _("QEMU reports migration is still running")); return NULL; } return qemuMigrationSrcBeginXML(vm, xmlin, cookieout, cookieoutlen, 0, NULL, 0, flags); } static char * qemuMigrationSrcBeginResumePhase(virConnectPtr conn, virDomainObj *vm, const char *xmlin, char **cookieout, int *cookieoutlen, unsigned int flags) { g_autofree char *xml = NULL; VIR_DEBUG("vm=%p", vm); if (!qemuMigrationAnyCanResume(vm, VIR_ASYNC_JOB_MIGRATION_OUT, flags, QEMU_MIGRATION_PHASE_POSTCOPY_FAILED)) return NULL; if (qemuMigrationJobStartPhase(vm, QEMU_MIGRATION_PHASE_BEGIN_RESUME) < 0) return NULL; virCloseCallbacksDomainRemove(vm, NULL, qemuMigrationAnyConnectionClosed); qemuDomainCleanupRemove(vm, qemuProcessCleanupMigrationJob); xml = qemuMigrationSrcBeginResume(vm, xmlin, cookieout, cookieoutlen, flags); virCloseCallbacksDomainAdd(vm, conn, qemuMigrationAnyConnectionClosed); if (!xml) ignore_value(qemuMigrationJobSetPhase(vm, QEMU_MIGRATION_PHASE_POSTCOPY_FAILED)); qemuMigrationJobContinue(vm, qemuProcessCleanupMigrationJob); return g_steal_pointer(&xml); } char * qemuMigrationSrcBegin(virConnectPtr conn, virDomainObj *vm, const char *xmlin, const char *dname, char **cookieout, int *cookieoutlen, size_t nmigrate_disks, const char **migrate_disks, unsigned int flags) { virQEMUDriver *driver = conn->privateData; g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver); g_autofree char *xml = NULL; char *ret = NULL; virDomainAsyncJob asyncJob; if (cfg->migrateTLSForce && !(flags & VIR_MIGRATE_TUNNELLED) && !(flags & VIR_MIGRATE_TLS)) { virReportError(VIR_ERR_OPERATION_INVALID, "%s", _("this libvirtd instance allows migration only with VIR_MIGRATE_TLS flag")); goto cleanup; } if (flags & VIR_MIGRATE_POSTCOPY_RESUME) { ret = qemuMigrationSrcBeginResumePhase(conn, vm, xmlin, cookieout, cookieoutlen, flags); goto cleanup; } if ((flags & VIR_MIGRATE_CHANGE_PROTECTION)) { if (qemuMigrationJobStart(vm, VIR_ASYNC_JOB_MIGRATION_OUT, flags) < 0) goto cleanup; asyncJob = VIR_ASYNC_JOB_MIGRATION_OUT; } else { if (!qemuMigrationJobIsAllowed(vm)) goto cleanup; if (virDomainObjBeginJob(vm, VIR_JOB_MODIFY) < 0) goto cleanup; asyncJob = VIR_ASYNC_JOB_NONE; } qemuMigrationSrcStoreDomainState(vm); if (!(flags & VIR_MIGRATE_OFFLINE) && virDomainObjCheckActive(vm) < 0) goto endjob; /* Check if there is any ejected media. * We don't want to require them on the destination. */ if (!(flags & VIR_MIGRATE_OFFLINE) && qemuProcessRefreshDisks(vm, asyncJob) < 0) goto endjob; if (!(xml = qemuMigrationSrcBeginPhase(driver, vm, xmlin, dname, cookieout, cookieoutlen, nmigrate_disks, migrate_disks, flags))) goto endjob; if ((flags & VIR_MIGRATE_CHANGE_PROTECTION)) { /* We keep the job active across API calls until the confirm() call. * This prevents any other APIs being invoked while migration is taking * place. */ virCloseCallbacksDomainAdd(vm, conn, qemuMigrationAnyConnectionClosed); } ret = g_steal_pointer(&xml); endjob: if (flags & VIR_MIGRATE_CHANGE_PROTECTION) { if (ret) qemuMigrationJobContinue(vm, qemuProcessCleanupMigrationJob); else qemuMigrationJobFinish(vm); } else { virDomainObjEndJob(vm); } cleanup: virDomainObjEndAPI(&vm); return ret; } /* Prepare is the first step, and it runs on the destination host. */ static void qemuMigrationDstPrepareCleanup(virQEMUDriver *driver, virDomainObj *vm) { qemuDomainObjPrivate *priv = vm->privateData; VIR_DEBUG("driver=%p, vm=%s, job=%s, asyncJob=%s", driver, vm->def->name, virDomainJobTypeToString(vm->job->active), virDomainAsyncJobTypeToString(vm->job->asyncJob)); virPortAllocatorRelease(priv->migrationPort); priv->migrationPort = 0; if (!qemuMigrationJobIsActive(vm, VIR_ASYNC_JOB_MIGRATION_IN)) return; qemuDomainObjDiscardAsyncJob(vm); } static qemuProcessIncomingDef * qemuMigrationDstPrepare(virDomainObj *vm, bool tunnel, const char *protocol, const char *listenAddress, unsigned short port, int fd) { qemuDomainObjPrivate *priv = vm->privateData; g_autofree char *migrateFrom = NULL; if (tunnel) { migrateFrom = g_strdup("stdio"); } else if (g_strcmp0(protocol, "unix") == 0) { migrateFrom = g_strdup_printf("%s:%s", protocol, listenAddress); } else { bool encloseAddress = false; bool hostIPv6Capable = false; struct addrinfo *info = NULL; struct addrinfo hints = { .ai_flags = AI_ADDRCONFIG, .ai_socktype = SOCK_STREAM }; const char *incFormat; if (getaddrinfo("::", NULL, &hints, &info) == 0) { freeaddrinfo(info); hostIPv6Capable = true; } if (listenAddress) { if (virSocketAddrNumericFamily(listenAddress) == AF_INET6) { if (!hostIPv6Capable) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("host isn't capable of IPv6")); return NULL; } /* IPv6 address must be escaped in brackets on the cmd line */ encloseAddress = true; } else { /* listenAddress is a hostname or IPv4 */ } } else if (hostIPv6Capable) { /* Listen on :: instead of 0.0.0.0 if QEMU understands it * and there is at least one IPv6 address configured */ listenAddress = "::"; encloseAddress = true; } else { listenAddress = "0.0.0.0"; } /* QEMU will be started with * -incoming protocol:[]:port, * -incoming protocol::port, or * -incoming protocol::port */ if (encloseAddress) incFormat = "%s:[%s]:%d"; else incFormat = "%s:%s:%d"; migrateFrom = g_strdup_printf(incFormat, protocol, listenAddress, port); } return qemuProcessIncomingDefNew(priv->qemuCaps, listenAddress, migrateFrom, fd, NULL); } /** * qemuMigrationDstPrepareAnyBlockDirtyBitmaps: * @vm: domain object * @mig: migration cookie * @migParams: migration parameters * @flags: migration flags * * Checks whether block dirty bitmaps offered by the migration source are * to be migrated (e.g. they don't exist, the destination is compatible etc) * and sets up destination qemu for migrating the bitmaps as well as updates the * list of eligible bitmaps in the migration cookie to be sent back to the * source. */ static int qemuMigrationDstPrepareAnyBlockDirtyBitmaps(virDomainObj *vm, qemuMigrationCookie *mig, qemuMigrationParams *migParams, unsigned int flags) { qemuDomainObjPrivate *priv = vm->privateData; g_autoptr(virJSONValue) mapping = NULL; g_autoptr(GHashTable) blockNamedNodeData = NULL; GSList *nextdisk; if (!mig->nbd || !mig->blockDirtyBitmaps || !(flags & (VIR_MIGRATE_NON_SHARED_DISK | VIR_MIGRATE_NON_SHARED_INC)) || !virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_MIGRATION_PARAM_BLOCK_BITMAP_MAPPING)) return 0; if (qemuMigrationCookieBlockDirtyBitmapsMatchDisks(vm->def, mig->blockDirtyBitmaps) < 0) return -1; if (!(blockNamedNodeData = qemuBlockGetNamedNodeData(vm, VIR_ASYNC_JOB_MIGRATION_IN))) return -1; for (nextdisk = mig->blockDirtyBitmaps; nextdisk; nextdisk = nextdisk->next) { qemuMigrationBlockDirtyBitmapsDisk *disk = nextdisk->data; qemuBlockNamedNodeData *nodedata; GSList *nextbitmap; if (!(nodedata = virHashLookup(blockNamedNodeData, disk->nodename))) { virReportError(VIR_ERR_INTERNAL_ERROR, _("failed to find data for block node '%1$s'"), disk->nodename); return -1; } /* Bitmaps can only be migrated to qcow2 v3+ */ if (disk->disk->src->format != VIR_STORAGE_FILE_QCOW2 || nodedata->qcow2v2) { disk->skip = true; continue; } for (nextbitmap = disk->bitmaps; nextbitmap; nextbitmap = nextbitmap->next) { qemuMigrationBlockDirtyBitmapsDiskBitmap *bitmap = nextbitmap->data; size_t k; /* don't migrate into existing bitmaps */ for (k = 0; k < nodedata->nbitmaps; k++) { if (STREQ(bitmap->bitmapname, nodedata->bitmaps[k]->name)) { bitmap->skip = true; break; } } if (bitmap->skip) continue; } } if (qemuMigrationCookieBlockDirtyBitmapsToParams(mig->blockDirtyBitmaps, &mapping) < 0) return -1; if (!mapping) return 0; qemuMigrationParamsSetBlockDirtyBitmapMapping(migParams, &mapping); mig->flags |= QEMU_MIGRATION_COOKIE_BLOCK_DIRTY_BITMAPS; return 0; } static int qemuMigrationDstPrepareActive(virQEMUDriver *driver, virDomainObj *vm, virConnectPtr dconn, qemuMigrationCookie *mig, virStreamPtr st, const char *protocol, unsigned short port, const char *listenAddress, size_t nmigrate_disks, const char **migrate_disks, int nbdPort, const char *nbdURI, qemuMigrationParams *migParams, unsigned int flags) { qemuDomainObjPrivate *priv = vm->privateData; qemuDomainJobPrivate *jobPriv = vm->job->privateData; qemuProcessIncomingDef *incoming = NULL; g_autofree char *tlsAlias = NULL; virObjectEvent *event = NULL; virErrorPtr origErr = NULL; int dataFD[2] = { -1, -1 }; bool stopProcess = false; unsigned int startFlags; bool relabel = false; bool tunnel = !!st; int ret = -1; int rv; if (STREQ_NULLABLE(protocol, "rdma") && !virMemoryLimitIsSet(vm->def->mem.hard_limit)) { virReportError(VIR_ERR_OPERATION_INVALID, "%s", _("cannot start RDMA migration with no memory hard limit set")); goto error; } if (qemuMigrationDstPrecreateStorage(vm, mig->nbd, nmigrate_disks, migrate_disks, !!(flags & VIR_MIGRATE_NON_SHARED_INC)) < 0) goto error; if (tunnel && virPipe(dataFD) < 0) goto error; startFlags = VIR_QEMU_PROCESS_START_AUTODESTROY; if (qemuProcessInit(driver, vm, mig->cpu, VIR_ASYNC_JOB_MIGRATION_IN, true, startFlags) < 0) goto error; stopProcess = true; if (!(incoming = qemuMigrationDstPrepare(vm, tunnel, protocol, listenAddress, port, dataFD[0]))) goto error; if (qemuProcessPrepareDomain(driver, vm, startFlags) < 0) goto error; if (qemuProcessPrepareHost(driver, vm, startFlags) < 0) goto error; rv = qemuProcessLaunch(dconn, driver, vm, VIR_ASYNC_JOB_MIGRATION_IN, incoming, NULL, VIR_NETDEV_VPORT_PROFILE_OP_MIGRATE_IN_START, startFlags); if (rv < 0) { if (rv == -2) relabel = true; goto error; } relabel = true; if (tunnel) { if (virFDStreamOpen(st, dataFD[1]) < 0) { virReportSystemError(errno, "%s", _("cannot pass pipe for tunnelled migration")); goto error; } dataFD[1] = -1; /* 'st' owns the FD now & will close it */ } if (STREQ_NULLABLE(protocol, "rdma") && virMemoryLimitIsSet(vm->def->mem.hard_limit) && qemuDomainSetMaxMemLock(vm, vm->def->mem.hard_limit << 10, &priv->preMigrationMemlock) < 0) { goto error; } if (qemuMigrationDstPrepareAnyBlockDirtyBitmaps(vm, mig, migParams, flags) < 0) goto error; if (qemuMigrationParamsCheck(vm, VIR_ASYNC_JOB_MIGRATION_IN, migParams, mig->caps->automatic) < 0) goto error; /* Save original migration parameters */ qemuDomainSaveStatus(vm); /* Migrations using TLS need to add the "tls-creds-x509" object and * set the migration TLS parameters */ if (flags & VIR_MIGRATE_TLS) { if (qemuMigrationParamsEnableTLS(driver, vm, true, VIR_ASYNC_JOB_MIGRATION_IN, &tlsAlias, NULL, migParams) < 0) goto error; } else { if (qemuMigrationParamsDisableTLS(vm, migParams) < 0) goto error; } if (qemuMigrationParamsApply(vm, VIR_ASYNC_JOB_MIGRATION_IN, migParams, flags) < 0) goto error; if (mig->nbd && flags & (VIR_MIGRATE_NON_SHARED_DISK | VIR_MIGRATE_NON_SHARED_INC)) { const char *nbdTLSAlias = NULL; if (flags & VIR_MIGRATE_TLS) { if (!virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_NBD_TLS)) { virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", _("QEMU NBD server does not support TLS transport")); goto error; } nbdTLSAlias = tlsAlias; } if (qemuMigrationDstStartNBDServer(driver, vm, incoming->address, nmigrate_disks, migrate_disks, nbdPort, nbdURI, nbdTLSAlias) < 0) { goto error; } } if (mig->lockState) { VIR_DEBUG("Received lockstate %s", mig->lockState); VIR_FREE(priv->lockState); priv->lockState = g_steal_pointer(&mig->lockState); } else { VIR_DEBUG("Received no lockstate"); } if (qemuMigrationDstRun(vm, incoming->uri, VIR_ASYNC_JOB_MIGRATION_IN) < 0) goto error; if (qemuProcessFinishStartup(driver, vm, VIR_ASYNC_JOB_MIGRATION_IN, false, VIR_DOMAIN_PAUSED_MIGRATION) < 0) goto error; virDomainAuditStart(vm, "migrated", true); event = virDomainEventLifecycleNewFromObj(vm, VIR_DOMAIN_EVENT_STARTED, VIR_DOMAIN_EVENT_STARTED_MIGRATED); ret = 0; cleanup: qemuProcessIncomingDefFree(incoming); VIR_FORCE_CLOSE(dataFD[0]); VIR_FORCE_CLOSE(dataFD[1]); virObjectEventStateQueue(driver->domainEventState, event); virErrorRestore(&origErr); return ret; error: virErrorPreserveLast(&origErr); qemuMigrationParamsReset(vm, VIR_ASYNC_JOB_MIGRATION_IN, jobPriv->migParams, vm->job->apiFlags); if (stopProcess) { unsigned int stopFlags = VIR_QEMU_PROCESS_STOP_MIGRATED; if (!relabel) stopFlags |= VIR_QEMU_PROCESS_STOP_NO_RELABEL; virDomainAuditStart(vm, "migrated", false); qemuProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_FAILED, VIR_ASYNC_JOB_MIGRATION_IN, stopFlags); /* release if port is auto selected which is not the case if * it is given in parameters */ if (nbdPort == 0) virPortAllocatorRelease(priv->nbdPort); priv->nbdPort = 0; } goto cleanup; } static int qemuMigrationDstPrepareFresh(virQEMUDriver *driver, virConnectPtr dconn, const char *cookiein, int cookieinlen, char **cookieout, int *cookieoutlen, virDomainDef **def, const char *origname, virStreamPtr st, const char *protocol, unsigned short port, bool autoPort, const char *listenAddress, size_t nmigrate_disks, const char **migrate_disks, int nbdPort, const char *nbdURI, qemuMigrationParams *migParams, unsigned int flags) { virDomainObj *vm = NULL; virErrorPtr origErr; int ret = -1; qemuDomainObjPrivate *priv = NULL; g_autoptr(qemuMigrationCookie) mig = NULL; g_autofree char *xmlout = NULL; unsigned int cookieFlags = 0; bool taint_hook = false; VIR_DEBUG("name=%s, origname=%s, protocol=%s, port=%hu, " "listenAddress=%s, nbdPort=%d, nbdURI=%s, flags=0x%x", (*def)->name, NULLSTR(origname), protocol, port, listenAddress, nbdPort, NULLSTR(nbdURI), flags); if (!(flags & VIR_MIGRATE_OFFLINE)) { cookieFlags = QEMU_MIGRATION_COOKIE_GRAPHICS | QEMU_MIGRATION_COOKIE_CAPS; } /* Let migration hook filter domain XML */ if (virHookPresent(VIR_HOOK_DRIVER_QEMU)) { g_autofree char *xml = NULL; int hookret; if (!(xml = qemuDomainDefFormatXML(driver, NULL, *def, VIR_DOMAIN_XML_SECURE | VIR_DOMAIN_XML_MIGRATABLE))) goto cleanup; hookret = virHookCall(VIR_HOOK_DRIVER_QEMU, (*def)->name, VIR_HOOK_QEMU_OP_MIGRATE, VIR_HOOK_SUBOP_BEGIN, NULL, xml, &xmlout); if (hookret < 0) { goto cleanup; } else if (hookret == 0) { if (virStringIsEmpty(xmlout)) { VIR_DEBUG("Migrate hook filter returned nothing; using the" " original XML"); } else { g_autoptr(virDomainDef) newdef = NULL; VIR_DEBUG("Using hook-filtered domain XML: %s", xmlout); newdef = virDomainDefParseString(xmlout, driver->xmlopt, NULL, VIR_DOMAIN_DEF_PARSE_INACTIVE | VIR_DOMAIN_DEF_PARSE_SKIP_VALIDATE); if (!newdef) goto cleanup; if (!qemuDomainDefCheckABIStability(driver, NULL, *def, newdef)) goto cleanup; virDomainDefFree(*def); *def = g_steal_pointer(&newdef); /* We should taint the domain here. However, @vm and therefore * privateData too are still NULL, so just notice the fact and * taint it later. */ taint_hook = true; } } } /* Parse cookie earlier than adding the domain onto the * domain list. Parsing/validation may fail and there's no * point in having the domain in the list at that point. */ if (!(mig = qemuMigrationCookieParse(driver, NULL, *def, origname, NULL, cookiein, cookieinlen, QEMU_MIGRATION_COOKIE_LOCKSTATE | QEMU_MIGRATION_COOKIE_NBD | QEMU_MIGRATION_COOKIE_MEMORY_HOTPLUG | QEMU_MIGRATION_COOKIE_CPU_HOTPLUG | QEMU_MIGRATION_COOKIE_CPU | QEMU_MIGRATION_COOKIE_CAPS | QEMU_MIGRATION_COOKIE_BLOCK_DIRTY_BITMAPS))) goto cleanup; if (!(vm = virDomainObjListAdd(driver->domains, def, driver->xmlopt, VIR_DOMAIN_OBJ_LIST_ADD_LIVE | VIR_DOMAIN_OBJ_LIST_ADD_CHECK_LIVE, NULL))) goto cleanup; priv = vm->privateData; priv->origname = g_strdup(origname); if (taint_hook) { /* Domain XML has been altered by a hook script. */ priv->hookRun = true; } if (qemuMigrationJobStart(vm, VIR_ASYNC_JOB_MIGRATION_IN, flags) < 0) goto cleanup; if (qemuMigrationJobStartPhase(vm, QEMU_MIGRATION_PHASE_PREPARE) < 0) goto stopjob; /* Domain starts inactive, even if the domain XML had an id field. */ vm->def->id = -1; if (!(flags & VIR_MIGRATE_OFFLINE)) { if (qemuMigrationDstPrepareActive(driver, vm, dconn, mig, st, protocol, port, listenAddress, nmigrate_disks, migrate_disks, nbdPort, nbdURI, migParams, flags) < 0) { goto stopjob; } if (mig->nbd && flags & (VIR_MIGRATE_NON_SHARED_DISK | VIR_MIGRATE_NON_SHARED_INC)) cookieFlags |= QEMU_MIGRATION_COOKIE_NBD; } if (qemuMigrationCookieFormat(mig, driver, vm, QEMU_MIGRATION_DESTINATION, cookieout, cookieoutlen, cookieFlags) < 0) { /* We could tear down the whole guest here, but * cookie data is (so far) non-critical, so that * seems a little harsh. We'll just warn for now. */ VIR_WARN("Unable to encode migration cookie"); } /* We keep the job active across API calls until the finish() call. * This prevents any other APIs being invoked while incoming * migration is taking place. */ qemuMigrationJobContinue(vm, qemuMigrationDstPrepareCleanup); if (autoPort) priv->migrationPort = port; /* in this case port is not auto selected and we don't need to manage it * anymore after cookie is baked */ if (nbdPort != 0) priv->nbdPort = 0; ret = 0; cleanup: virErrorPreserveLast(&origErr); if (ret < 0 && priv) { /* priv is set right after vm is added to the list of domains * and there is no 'goto cleanup;' in the middle of those */ VIR_FREE(priv->origname); virDomainObjRemoveTransientDef(vm); qemuDomainRemoveInactive(driver, vm, 0, false); } virDomainObjEndAPI(&vm); virErrorRestore(&origErr); return ret; stopjob: qemuMigrationJobFinish(vm); goto cleanup; } static int qemuMigrationDstPrepareResume(virQEMUDriver *driver, virConnectPtr conn, const char *cookiein, int cookieinlen, char **cookieout, int *cookieoutlen, virDomainDef *def, const char *origname, const char *protocol, unsigned short port, bool autoPort, const char *listenAddress, unsigned int flags) { g_autoptr(qemuMigrationCookie) mig = NULL; qemuProcessIncomingDef *incoming = NULL; qemuDomainObjPrivate *priv; virDomainJobStatus status; virDomainObj *vm; int ret = -1; VIR_DEBUG("name=%s, origname=%s, protocol=%s, port=%hu, " "listenAddress=%s, flags=0x%x", def->name, NULLSTR(origname), protocol, port, NULLSTR(listenAddress), flags); vm = virDomainObjListFindByName(driver->domains, def->name); if (!vm) { virReportError(VIR_ERR_NO_DOMAIN, _("no domain with matching name '%1$s'"), def->name); qemuMigrationDstErrorReport(driver, def->name); return -1; } priv = vm->privateData; if (!qemuMigrationAnyCanResume(vm, VIR_ASYNC_JOB_MIGRATION_IN, flags, QEMU_MIGRATION_PHASE_POSTCOPY_FAILED)) goto cleanup; if (qemuMigrationJobStartPhase(vm, QEMU_MIGRATION_PHASE_PREPARE_RESUME) < 0) goto cleanup; qemuDomainCleanupRemove(vm, qemuProcessCleanupMigrationJob); if (qemuMigrationAnyRefreshStatus(vm, VIR_ASYNC_JOB_MIGRATION_IN, &status) < 0) goto cleanup; if (status != VIR_DOMAIN_JOB_STATUS_POSTCOPY_PAUSED) { virReportError(VIR_ERR_OPERATION_INVALID, "%s", _("QEMU reports migration is still running")); goto cleanup; } if (!(mig = qemuMigrationCookieParse(driver, vm, def, origname, NULL, cookiein, cookieinlen, QEMU_MIGRATION_COOKIE_CAPS))) goto cleanup; priv->origname = g_strdup(origname); if (!(incoming = qemuMigrationDstPrepare(vm, false, protocol, listenAddress, port, -1))) goto cleanup; if (qemuDomainObjEnterMonitorAsync(vm, VIR_ASYNC_JOB_MIGRATION_IN) < 0) goto cleanup; ret = qemuMonitorMigrateRecover(priv->mon, incoming->uri); qemuDomainObjExitMonitor(vm); if (ret < 0) goto cleanup; if (qemuMigrationCookieFormat(mig, driver, vm, QEMU_MIGRATION_DESTINATION, cookieout, cookieoutlen, QEMU_MIGRATION_COOKIE_CAPS) < 0) VIR_WARN("Unable to encode migration cookie"); virCloseCallbacksDomainAdd(vm, conn, qemuMigrationAnyConnectionClosed); if (autoPort) priv->migrationPort = port; cleanup: qemuProcessIncomingDefFree(incoming); if (ret < 0) { VIR_FREE(priv->origname); ignore_value(qemuMigrationJobSetPhase(vm, QEMU_MIGRATION_PHASE_POSTCOPY_FAILED)); } qemuMigrationJobContinue(vm, qemuProcessCleanupMigrationJob); virDomainObjEndAPI(&vm); return ret; } static int qemuMigrationDstPrepareAny(virQEMUDriver *driver, virConnectPtr dconn, const char *cookiein, int cookieinlen, char **cookieout, int *cookieoutlen, virDomainDef **def, const char *origname, virStreamPtr st, const char *protocol, unsigned short port, bool autoPort, const char *listenAddress, size_t nmigrate_disks, const char **migrate_disks, int nbdPort, const char *nbdURI, qemuMigrationParams *migParams, unsigned int flags) { g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver); if (flags & VIR_MIGRATE_OFFLINE) { if (flags & (VIR_MIGRATE_NON_SHARED_DISK | VIR_MIGRATE_NON_SHARED_INC)) { virReportError(VIR_ERR_OPERATION_INVALID, "%s", _("offline migration cannot handle non-shared storage")); return -1; } if (!(flags & VIR_MIGRATE_PERSIST_DEST)) { virReportError(VIR_ERR_OPERATION_INVALID, "%s", _("offline migration must be specified with the persistent flag set")); return -1; } if (st) { virReportError(VIR_ERR_OPERATION_INVALID, "%s", _("tunnelled offline migration does not make sense")); return -1; } } if (flags & VIR_MIGRATE_POSTCOPY && (!(flags & VIR_MIGRATE_LIVE) || flags & VIR_MIGRATE_PAUSED)) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("post-copy migration is not supported with non-live or paused migration")); return -1; } if (flags & VIR_MIGRATE_POSTCOPY && flags & VIR_MIGRATE_TUNNELLED) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("post-copy is not supported with tunnelled migration")); return -1; } if (cfg->migrateTLSForce && !(flags & VIR_MIGRATE_TUNNELLED) && !(flags & VIR_MIGRATE_TLS)) { virReportError(VIR_ERR_OPERATION_INVALID, "%s", _("this libvirtd instance allows migration only with VIR_MIGRATE_TLS flag")); return -1; } if (flags & VIR_MIGRATE_POSTCOPY_RESUME) { return qemuMigrationDstPrepareResume(driver, dconn, cookiein, cookieinlen, cookieout, cookieoutlen, *def, origname, protocol, port, autoPort, listenAddress, flags); } return qemuMigrationDstPrepareFresh(driver, dconn, cookiein, cookieinlen, cookieout, cookieoutlen, def, origname, st, protocol, port, autoPort, listenAddress, nmigrate_disks, migrate_disks, nbdPort, nbdURI, migParams, flags); } /* * This version starts an empty VM listening on a localhost TCP port, and * sets up the corresponding virStream to handle the incoming data. */ int qemuMigrationDstPrepareTunnel(virQEMUDriver *driver, virConnectPtr dconn, const char *cookiein, int cookieinlen, char **cookieout, int *cookieoutlen, virStreamPtr st, virDomainDef **def, const char *origname, qemuMigrationParams *migParams, unsigned int flags) { VIR_DEBUG("driver=%p, dconn=%p, cookiein=%s, cookieinlen=%d, " "cookieout=%p, cookieoutlen=%p, st=%p, def=%p, " "origname=%s, flags=0x%x", driver, dconn, NULLSTR(cookiein), cookieinlen, cookieout, cookieoutlen, st, *def, origname, flags); if (st == NULL) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("tunnelled migration requested but NULL stream passed")); return -1; } return qemuMigrationDstPrepareAny(driver, dconn, cookiein, cookieinlen, cookieout, cookieoutlen, def, origname, st, NULL, 0, false, NULL, 0, NULL, 0, NULL, migParams, flags); } static virURI * qemuMigrationAnyParseURI(const char *uri, bool *wellFormed) { char *tmp = NULL; virURI *parsed; /* For compatibility reasons tcp://... URIs are sent as tcp:... * We need to transform them to a well-formed URI before parsing. */ if (STRPREFIX(uri, "tcp:") && !STRPREFIX(uri + 4, "//")) { tmp = g_strdup_printf("tcp://%s", uri + 4); uri = tmp; } parsed = virURIParse(uri); if (parsed && wellFormed) *wellFormed = !tmp; VIR_FREE(tmp); return parsed; } int qemuMigrationDstPrepareDirect(virQEMUDriver *driver, virConnectPtr dconn, const char *cookiein, int cookieinlen, char **cookieout, int *cookieoutlen, const char *uri_in, char **uri_out, virDomainDef **def, const char *origname, const char *listenAddress, size_t nmigrate_disks, const char **migrate_disks, int nbdPort, const char *nbdURI, qemuMigrationParams *migParams, unsigned int flags) { unsigned short port = 0; bool autoPort = true; g_autofree char *hostname = NULL; int ret = -1; g_autoptr(virURI) uri = NULL; g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver); const char *migrateHost = cfg->migrateHost; VIR_DEBUG("driver=%p, dconn=%p, cookiein=%s, cookieinlen=%d, " "cookieout=%p, cookieoutlen=%p, uri_in=%s, uri_out=%p, " "def=%p, origname=%s, listenAddress=%s, " "nmigrate_disks=%zu, migrate_disks=%p, nbdPort=%d, " "nbdURI=%s, flags=0x%x", driver, dconn, NULLSTR(cookiein), cookieinlen, cookieout, cookieoutlen, NULLSTR(uri_in), uri_out, *def, origname, NULLSTR(listenAddress), nmigrate_disks, migrate_disks, nbdPort, NULLSTR(nbdURI), flags); *uri_out = NULL; /* The URI passed in may be NULL or a string "tcp://somehostname:port". * * If the URI passed in is NULL then we allocate a port number * from our pool of port numbers, and if the migrateHost is configured, * we return a URI of "tcp://migrateHost:port", otherwise return a URI * of "tcp://ourhostname:port". * * If the URI passed in is not NULL then we try to parse out the * port number and use that (note that the hostname is assumed * to be a correct hostname which refers to the target machine). */ if (uri_in == NULL) { bool encloseAddress = false; const char *incFormat; if (virPortAllocatorAcquire(driver->migrationPorts, &port) < 0) goto cleanup; if (migrateHost != NULL) { if (virSocketAddrNumericFamily(migrateHost) == AF_INET6) encloseAddress = true; hostname = g_strdup(migrateHost); } else { if ((hostname = virGetHostname()) == NULL) goto cleanup; } if (STRPREFIX(hostname, "localhost")) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("hostname on destination resolved to localhost," " but migration requires an FQDN")); goto cleanup; } /* XXX this really should have been a properly well-formed * URI, but we can't add in tcp:// now without breaking * compatibility with old targets. We at least make the * new targets accept both syntaxes though. */ if (encloseAddress) incFormat = "%s:[%s]:%d"; else incFormat = "%s:%s:%d"; *uri_out = g_strdup_printf(incFormat, "tcp", hostname, port); } else { bool well_formed_uri = false; if (!(uri = qemuMigrationAnyParseURI(uri_in, &well_formed_uri))) goto cleanup; if (uri->scheme == NULL) { virReportError(VIR_ERR_INVALID_ARG, _("missing scheme in migration URI: %1$s"), uri_in); goto cleanup; } if (STRNEQ(uri->scheme, "tcp") && STRNEQ(uri->scheme, "rdma") && STRNEQ(uri->scheme, "unix")) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, _("unsupported scheme %1$s in migration URI %2$s"), uri->scheme, uri_in); goto cleanup; } if (STREQ(uri->scheme, "unix")) { autoPort = false; listenAddress = uri->path; } else { if (uri->server == NULL) { virReportError(VIR_ERR_INVALID_ARG, _("missing host in migration URI: %1$s"), uri_in); goto cleanup; } if (uri->port == 0) { if (virPortAllocatorAcquire(driver->migrationPorts, &port) < 0) goto cleanup; /* Send well-formed URI only if uri_in was well-formed */ if (well_formed_uri) { uri->port = port; *uri_out = virURIFormat(uri); } else { *uri_out = g_strdup_printf("%s:%d", uri_in, port); } } else { port = uri->port; autoPort = false; } } } if (*uri_out) VIR_DEBUG("Generated uri_out=%s", *uri_out); ret = qemuMigrationDstPrepareAny(driver, dconn, cookiein, cookieinlen, cookieout, cookieoutlen, def, origname, NULL, uri ? uri->scheme : "tcp", port, autoPort, listenAddress, nmigrate_disks, migrate_disks, nbdPort, nbdURI, migParams, flags); cleanup: if (ret != 0) { VIR_FREE(*uri_out); if (autoPort) virPortAllocatorRelease(port); } return ret; } virDomainDef * qemuMigrationAnyPrepareDef(virQEMUDriver *driver, virQEMUCaps *qemuCaps, const char *dom_xml, const char *dname, char **origname) { virDomainDef *def; char *name = NULL; if (!dom_xml) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("no domain XML passed")); return NULL; } if (!(def = virDomainDefParseString(dom_xml, driver->xmlopt, qemuCaps, VIR_DOMAIN_DEF_PARSE_INACTIVE | VIR_DOMAIN_DEF_PARSE_SKIP_VALIDATE))) goto cleanup; if (dname) { name = def->name; def->name = g_strdup(dname); } cleanup: if (def && origname) *origname = name; else VIR_FREE(name); return def; } void qemuMigrationSrcComplete(virQEMUDriver *driver, virDomainObj *vm, virDomainAsyncJob asyncJob) { qemuDomainObjPrivate *priv = vm->privateData; virDomainJobData *jobData = vm->job->completed; virObjectEvent *event; int reason; if (!jobData) { vm->job->completed = virDomainJobDataCopy(vm->job->current); jobData = vm->job->completed; jobData->status = VIR_DOMAIN_JOB_STATUS_COMPLETED; } /* We need to refresh migration statistics after a completed post-copy * migration since jobData contains obsolete data from the time we * switched to post-copy mode. */ if (virDomainObjGetState(vm, &reason) == VIR_DOMAIN_PAUSED && reason == VIR_DOMAIN_PAUSED_POSTCOPY) { VIR_DEBUG("Refreshing migration statistics"); if (qemuMigrationAnyFetchStats(vm, VIR_ASYNC_JOB_MIGRATION_OUT, jobData, NULL) < 0) VIR_WARN("Could not refresh migration statistics"); } qemuDomainJobDataUpdateTime(jobData); /* If guest uses SPICE and supports seamless migration we have to hold * up domain shutdown until SPICE server transfers its data */ qemuMigrationSrcWaitForSpice(vm); qemuProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_MIGRATED, asyncJob, VIR_QEMU_PROCESS_STOP_MIGRATED); virDomainAuditStop(vm, "migrated"); event = virDomainEventLifecycleNewFromObj(vm, VIR_DOMAIN_EVENT_STOPPED, VIR_DOMAIN_EVENT_STOPPED_MIGRATED); virObjectEventStateQueue(driver->domainEventState, event); qemuDomainEventEmitJobCompleted(driver, vm); priv->preMigrationMemlock = 0; } static int qemuMigrationSrcConfirmPhase(virQEMUDriver *driver, virDomainObj *vm, const char *cookiein, int cookieinlen, unsigned int flags, int retcode) { g_autoptr(qemuMigrationCookie) mig = NULL; qemuDomainObjPrivate *priv = vm->privateData; qemuDomainJobPrivate *jobPriv = vm->job->privateData; qemuDomainJobDataPrivate *currentData = vm->job->current->privateData; virDomainJobData *jobData = NULL; qemuMigrationJobPhase phase; VIR_DEBUG("driver=%p, vm=%p, cookiein=%s, cookieinlen=%d, " "flags=0x%x, retcode=%d", driver, vm, NULLSTR(cookiein), cookieinlen, flags, retcode); virCheckFlags(QEMU_MIGRATION_FLAGS, -1); if (retcode != 0 && virDomainObjIsPostcopy(vm, vm->job) && currentData->stats.mig.status == QEMU_MONITOR_MIGRATION_STATUS_COMPLETED) { VIR_DEBUG("Finish phase failed, but QEMU reports post-copy migration is completed; forcing success"); retcode = 0; } if (flags & VIR_MIGRATE_POSTCOPY_RESUME) { phase = QEMU_MIGRATION_PHASE_CONFIRM_RESUME; } else if (virDomainObjIsFailedPostcopy(vm, vm->job)) { /* Keep the original migration phase in case post-copy failed as the * job will stay active even though migration API finishes with an * error. */ phase = vm->job->phase; } else if (retcode == 0) { phase = QEMU_MIGRATION_PHASE_CONFIRM3; } else { phase = QEMU_MIGRATION_PHASE_CONFIRM3_CANCELLED; } if (qemuMigrationJobStartPhase(vm, phase) < 0) return -1; if (!(mig = qemuMigrationCookieParse(driver, vm, vm->def, priv->origname, priv->qemuCaps, cookiein, cookieinlen, QEMU_MIGRATION_COOKIE_STATS))) return -1; if (retcode == 0) jobData = vm->job->completed; else g_clear_pointer(&vm->job->completed, virDomainJobDataFree); /* Update times with the values sent by the destination daemon */ if (mig->jobData && jobData) { qemuDomainJobDataPrivate *privJob = jobData->privateData; qemuDomainJobDataPrivate *privMigJob = mig->jobData->privateData; jobData->timeDeltaSet = mig->jobData->timeDeltaSet; jobData->timeDelta = mig->jobData->timeDelta; privJob->stats.mig.downtime_set = privMigJob->stats.mig.downtime_set; privJob->stats.mig.downtime = privMigJob->stats.mig.downtime; } if (flags & VIR_MIGRATE_OFFLINE) return 0; /* Did the migration go as planned? If yes, kill off the domain object. * If something failed, resume CPUs, but only if we didn't use post-copy. */ if (retcode == 0) { qemuMigrationSrcComplete(driver, vm, VIR_ASYNC_JOB_MIGRATION_OUT); } else { virErrorPtr orig_err; int reason; virErrorPreserveLast(&orig_err); /* cancel any outstanding NBD jobs */ qemuMigrationSrcNBDCopyCancel(vm, false, VIR_ASYNC_JOB_MIGRATION_OUT, NULL); virErrorRestore(&orig_err); if (virDomainObjGetState(vm, &reason) == VIR_DOMAIN_PAUSED && reason == VIR_DOMAIN_PAUSED_POSTCOPY) { qemuMigrationSrcPostcopyFailed(vm); } else if (!virDomainObjIsFailedPostcopy(vm, vm->job)) { qemuMigrationSrcRestoreDomainState(driver, vm); qemuMigrationParamsReset(vm, VIR_ASYNC_JOB_MIGRATION_OUT, jobPriv->migParams, vm->job->apiFlags); qemuDomainSetMaxMemLock(vm, 0, &priv->preMigrationMemlock); } qemuDomainSaveStatus(vm); } return 0; } int qemuMigrationSrcConfirm(virQEMUDriver *driver, virDomainObj *vm, const char *cookiein, int cookieinlen, unsigned int flags, int cancelled) { qemuMigrationJobPhase phase; g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver); int ret = -1; VIR_DEBUG("vm=%p, flags=0x%x, cancelled=%d", vm, flags, cancelled); if (flags & VIR_MIGRATE_POSTCOPY_RESUME) { if (!qemuMigrationAnyCanResume(vm, VIR_ASYNC_JOB_MIGRATION_OUT, flags, QEMU_MIGRATION_PHASE_PERFORM_RESUME)) goto cleanup; phase = QEMU_MIGRATION_PHASE_CONFIRM_RESUME; } else { if (!qemuMigrationJobIsActive(vm, VIR_ASYNC_JOB_MIGRATION_OUT)) goto cleanup; /* Keep the original migration phase in case post-copy failed as the * job will stay active even though migration API finishes with an * error. */ if (virDomainObjIsFailedPostcopy(vm, vm->job)) phase = vm->job->phase; else if (cancelled) phase = QEMU_MIGRATION_PHASE_CONFIRM3_CANCELLED; else phase = QEMU_MIGRATION_PHASE_CONFIRM3; } if (qemuMigrationJobStartPhase(vm, phase) < 0) goto cleanup; virCloseCallbacksDomainRemove(vm, NULL, qemuMigrationAnyConnectionClosed); qemuDomainCleanupRemove(vm, qemuProcessCleanupMigrationJob); ret = qemuMigrationSrcConfirmPhase(driver, vm, cookiein, cookieinlen, flags, cancelled); if (virDomainObjIsFailedPostcopy(vm, vm->job)) { ignore_value(qemuMigrationJobSetPhase(vm, QEMU_MIGRATION_PHASE_POSTCOPY_FAILED)); qemuMigrationJobContinue(vm, qemuProcessCleanupMigrationJob); } else { qemuMigrationJobFinish(vm); } if (!virDomainObjIsActive(vm)) { if (!cancelled && ret == 0 && flags & VIR_MIGRATE_UNDEFINE_SOURCE) { virDomainDeleteConfig(cfg->configDir, cfg->autostartDir, vm); vm->persistent = 0; } qemuDomainRemoveInactive(driver, vm, VIR_DOMAIN_UNDEFINE_TPM, true); } cleanup: virDomainObjEndAPI(&vm); return ret; } enum qemuMigrationDestinationType { MIGRATION_DEST_HOST, MIGRATION_DEST_CONNECT_HOST, MIGRATION_DEST_SOCKET, MIGRATION_DEST_CONNECT_SOCKET, MIGRATION_DEST_FD, }; enum qemuMigrationForwardType { MIGRATION_FWD_DIRECT, MIGRATION_FWD_STREAM, }; typedef struct _qemuMigrationSpec qemuMigrationSpec; struct _qemuMigrationSpec { enum qemuMigrationDestinationType destType; union { struct { const char *protocol; const char *name; int port; } host; struct { const char *path; } socket; struct { int qemu; int local; } fd; } dest; enum qemuMigrationForwardType fwdType; union { virStreamPtr stream; } fwd; }; #define TUNNEL_SEND_BUF_SIZE 65536 typedef struct _qemuMigrationIOThread qemuMigrationIOThread; struct _qemuMigrationIOThread { virThread thread; virStreamPtr st; int sock; virError err; int wakeupRecvFD; int wakeupSendFD; }; static void qemuMigrationSrcIOFunc(void *arg) { qemuMigrationIOThread *data = arg; char *buffer = NULL; struct pollfd fds[2]; int timeout = -1; virErrorPtr err = NULL; VIR_DEBUG("Running migration tunnel; stream=%p, sock=%d", data->st, data->sock); buffer = g_new0(char, TUNNEL_SEND_BUF_SIZE); fds[0].fd = data->sock; fds[1].fd = data->wakeupRecvFD; for (;;) { int ret; fds[0].events = fds[1].events = POLLIN; fds[0].revents = fds[1].revents = 0; ret = poll(fds, G_N_ELEMENTS(fds), timeout); if (ret < 0) { if (errno == EAGAIN || errno == EINTR) continue; virReportSystemError(errno, "%s", _("poll failed in migration tunnel")); goto abrt; } if (ret == 0) { /* We were asked to gracefully stop but reading would block. This * can only happen if qemu told us migration finished but didn't * close the migration fd. We handle this in the same way as EOF. */ VIR_DEBUG("QEMU forgot to close migration fd"); break; } if (fds[1].revents & (POLLIN | POLLERR | POLLHUP)) { char stop = 0; if (saferead(data->wakeupRecvFD, &stop, 1) != 1) { virReportSystemError(errno, "%s", _("failed to read from wakeup fd")); goto abrt; } if (stop) { VIR_DEBUG("Migration tunnel was asked to abort"); goto abrt; } else { VIR_DEBUG("Migration tunnel was asked to finish"); timeout = 0; } } if (fds[0].revents & (POLLIN | POLLERR | POLLHUP)) { int nbytes; nbytes = saferead(data->sock, buffer, TUNNEL_SEND_BUF_SIZE); if (nbytes > 0) { if (virStreamSend(data->st, buffer, nbytes) < 0) goto error; } else if (nbytes < 0) { virReportSystemError(errno, "%s", _("tunnelled migration failed to read from qemu")); goto abrt; } else { /* EOF; get out of here */ break; } } } if (virStreamFinish(data->st) < 0) goto error; VIR_FORCE_CLOSE(data->sock); VIR_FREE(buffer); return; abrt: virErrorPreserveLast(&err); if (err && err->code == VIR_ERR_OK) { g_clear_pointer(&err, virFreeError); } virStreamAbort(data->st); virErrorRestore(&err); error: /* Let the source qemu know that the transfer can't continue anymore. * Don't copy the error for EPIPE as destination has the actual error. */ VIR_FORCE_CLOSE(data->sock); if (!virLastErrorIsSystemErrno(EPIPE)) virCopyLastError(&data->err); virResetLastError(); VIR_FREE(buffer); } static qemuMigrationIOThread * qemuMigrationSrcStartTunnel(virStreamPtr st, int sock) { qemuMigrationIOThread *io = NULL; int wakeupFD[2] = { -1, -1 }; if (virPipe(wakeupFD) < 0) goto error; io = g_new0(qemuMigrationIOThread, 1); io->st = st; io->sock = sock; io->wakeupRecvFD = wakeupFD[0]; io->wakeupSendFD = wakeupFD[1]; if (virThreadCreateFull(&io->thread, true, qemuMigrationSrcIOFunc, "qemu-mig-tunnel", false, io) < 0) { virReportSystemError(errno, "%s", _("Unable to create migration thread")); goto error; } return io; error: VIR_FORCE_CLOSE(wakeupFD[0]); VIR_FORCE_CLOSE(wakeupFD[1]); VIR_FREE(io); return NULL; } static int qemuMigrationSrcStopTunnel(qemuMigrationIOThread *io, bool error) { int rv = -1; char stop = error ? 1 : 0; /* make sure the thread finishes its job and is joinable */ if (safewrite(io->wakeupSendFD, &stop, 1) != 1) { virReportSystemError(errno, "%s", _("failed to wakeup migration tunnel")); goto cleanup; } virThreadJoin(&io->thread); /* Forward error from the IO thread, to this thread */ if (io->err.code != VIR_ERR_OK) { if (error) rv = 0; else virSetError(&io->err); virResetError(&io->err); goto cleanup; } rv = 0; cleanup: VIR_FORCE_CLOSE(io->wakeupSendFD); VIR_FORCE_CLOSE(io->wakeupRecvFD); VIR_FREE(io); return rv; } static int qemuMigrationSrcConnect(virQEMUDriver *driver, virDomainObj *vm, qemuMigrationSpec *spec) { virNetSocket *sock; g_autofree char *port = NULL; int fd_qemu = -1; int ret = -1; if (qemuSecuritySetSocketLabel(driver->securityManager, vm->def) < 0) goto cleanup; switch (spec->destType) { case MIGRATION_DEST_CONNECT_HOST: port = g_strdup_printf("%d", spec->dest.host.port); if (virNetSocketNewConnectTCP(spec->dest.host.name, port, AF_UNSPEC, &sock) == 0) { fd_qemu = virNetSocketDupFD(sock, true); virObjectUnref(sock); } break; case MIGRATION_DEST_CONNECT_SOCKET: if (virNetSocketNewConnectUNIX(spec->dest.socket.path, NULL, &sock) == 0) { fd_qemu = virNetSocketDupFD(sock, true); virObjectUnref(sock); } break; case MIGRATION_DEST_HOST: case MIGRATION_DEST_SOCKET: case MIGRATION_DEST_FD: break; } spec->destType = MIGRATION_DEST_FD; spec->dest.fd.qemu = fd_qemu; if (qemuSecurityClearSocketLabel(driver->securityManager, vm->def) < 0 || spec->dest.fd.qemu == -1) goto cleanup; /* Migration expects a blocking FD */ if (virSetBlocking(spec->dest.fd.qemu, true) < 0) { virReportSystemError(errno, _("Unable to set FD %1$d blocking"), spec->dest.fd.qemu); goto cleanup; } ret = 0; cleanup: if (ret < 0) VIR_FORCE_CLOSE(spec->dest.fd.qemu); return ret; } static int qemuMigrationSrcContinue(virDomainObj *vm, qemuMonitorMigrationStatus status, virDomainAsyncJob asyncJob) { qemuDomainObjPrivate *priv = vm->privateData; int ret; if (qemuDomainObjEnterMonitorAsync(vm, asyncJob) < 0) return -1; ret = qemuMonitorMigrateContinue(priv->mon, status); qemuDomainObjExitMonitor(vm); return ret; } static int qemuMigrationSetDBusVMState(virQEMUDriver *driver, virDomainObj *vm) { qemuDomainObjPrivate *priv = vm->privateData; if (priv->dbusVMStateIds) { int rv; if (qemuHotplugAttachDBusVMState(driver, vm, VIR_ASYNC_JOB_NONE) < 0) return -1; if (qemuDomainObjEnterMonitorAsync(vm, VIR_ASYNC_JOB_NONE) < 0) return -1; rv = qemuMonitorSetDBusVMStateIdList(priv->mon, priv->dbusVMStateIds); qemuDomainObjExitMonitor(vm); return rv; } else { if (qemuHotplugRemoveDBusVMState(vm, VIR_ASYNC_JOB_NONE) < 0) return -1; } return 0; } /** * qemuMigrationSrcRunPrepareBlockDirtyBitmapsMerge: * @vm: domain object * @mig: migration cookie * * When migrating full disks, which means that the backing chain of the disk * will be squashed into a single image we need to calculate bitmaps * corresponding to the checkpoints which express the same set of changes * for migration. * * This function prepares temporary bitmaps and corresponding merges, updates * the data so that the temporary bitmaps are used and registers the temporary * bitmaps for deletion on failed migration. */ static int qemuMigrationSrcRunPrepareBlockDirtyBitmapsMerge(virDomainObj *vm, qemuMigrationCookie *mig) { g_autoslist(qemuDomainJobPrivateMigrateTempBitmap) tmpbitmaps = NULL; qemuDomainObjPrivate *priv = vm->privateData; qemuDomainJobPrivate *jobPriv = vm->job->privateData; g_autoptr(virJSONValue) actions = virJSONValueNewArray(); g_autoptr(GHashTable) blockNamedNodeData = NULL; GSList *nextdisk; int rc; if (!(blockNamedNodeData = qemuBlockGetNamedNodeData(vm, VIR_ASYNC_JOB_MIGRATION_OUT))) return -1; for (nextdisk = mig->blockDirtyBitmaps; nextdisk; nextdisk = nextdisk->next) { qemuMigrationBlockDirtyBitmapsDisk *disk = nextdisk->data; GSList *nextbitmap; /* if a disk doesn't have a backing chain we don't need the code below */ if (!virStorageSourceHasBacking(disk->disk->src)) continue; for (nextbitmap = disk->bitmaps; nextbitmap; nextbitmap = nextbitmap->next) { qemuMigrationBlockDirtyBitmapsDiskBitmap *bitmap = nextbitmap->data; qemuDomainJobPrivateMigrateTempBitmap *tmpbmp; virStorageSource *n; unsigned long long granularity = 0; g_autoptr(virJSONValue) merge = virJSONValueNewArray(); for (n = disk->disk->src; virStorageSourceIsBacking(n); n = n->backingStore) { qemuBlockNamedNodeDataBitmap *b; if (!(b = qemuBlockNamedNodeDataGetBitmapByName(blockNamedNodeData, n, bitmap->bitmapname))) break; if (granularity == 0) granularity = b->granularity; if (qemuMonitorTransactionBitmapMergeSourceAddBitmap(merge, n->nodeformat, b->name) < 0) return -1; } bitmap->sourcebitmap = g_strdup_printf("libvirt-migration-%s", bitmap->alias); bitmap->persistent = VIR_TRISTATE_BOOL_YES; if (qemuMonitorTransactionBitmapAdd(actions, disk->disk->src->nodeformat, bitmap->sourcebitmap, false, false, granularity) < 0) return -1; if (qemuMonitorTransactionBitmapMerge(actions, disk->disk->src->nodeformat, bitmap->sourcebitmap, &merge) < 0) return -1; tmpbmp = g_new0(qemuDomainJobPrivateMigrateTempBitmap, 1); tmpbmp->nodename = g_strdup(disk->disk->src->nodeformat); tmpbmp->bitmapname = g_strdup(bitmap->sourcebitmap); tmpbitmaps = g_slist_prepend(tmpbitmaps, tmpbmp); } } if (qemuDomainObjEnterMonitorAsync(vm, VIR_ASYNC_JOB_MIGRATION_OUT) < 0) return -1; rc = qemuMonitorTransaction(priv->mon, &actions); qemuDomainObjExitMonitor(vm); if (rc < 0) return -1; jobPriv->migTempBitmaps = g_steal_pointer(&tmpbitmaps); return 0; } /** * qemuMigrationSrcRunPrepareBlockDirtyBitmaps: * @vm: domain object * @mig: migration cookie * @migParams: migration parameters * @flags: migration flags * * Configures the source for bitmap migration when the destination asks * for bitmaps. */ static int qemuMigrationSrcRunPrepareBlockDirtyBitmaps(virDomainObj *vm, qemuMigrationCookie *mig, qemuMigrationParams *migParams, unsigned int flags) { g_autoptr(virJSONValue) mapping = NULL; if (!mig->blockDirtyBitmaps) return 0; if (qemuMigrationCookieBlockDirtyBitmapsMatchDisks(vm->def, mig->blockDirtyBitmaps) < 0) return -1; /* For VIR_MIGRATE_NON_SHARED_INC we can migrate the bitmaps directly, * otherwise we must create merged bitmaps from the whole chain */ if (!(flags & VIR_MIGRATE_NON_SHARED_INC) && qemuMigrationSrcRunPrepareBlockDirtyBitmapsMerge(vm, mig) < 0) return -1; if (qemuMigrationCookieBlockDirtyBitmapsToParams(mig->blockDirtyBitmaps, &mapping) < 0) return -1; qemuMigrationParamsSetBlockDirtyBitmapMapping(migParams, &mapping); return 0; } /* The caller is supposed to enter monitor before calling this. */ static int qemuMigrationSrcStart(virDomainObj *vm, qemuMigrationSpec *spec, unsigned int migrateFlags, int *tunnelFd) { qemuDomainObjPrivate *priv = vm->privateData; virQEMUDriver *driver = priv->driver; g_autofree char *timestamp = NULL; int rc; /* connect to the destination qemu if needed */ if ((spec->destType == MIGRATION_DEST_CONNECT_HOST || spec->destType == MIGRATION_DEST_CONNECT_SOCKET) && qemuMigrationSrcConnect(driver, vm, spec) < 0) { return -1; } /* log start of migration */ if ((timestamp = virTimeStringNow()) != NULL) { if (migrateFlags & QEMU_MONITOR_MIGRATE_RESUME) qemuDomainLogAppendMessage(driver, vm, "%s: resuming migration\n", timestamp); else qemuDomainLogAppendMessage(driver, vm, "%s: initiating migration\n", timestamp); } switch (spec->destType) { case MIGRATION_DEST_HOST: if (STREQ(spec->dest.host.protocol, "rdma") && virMemoryLimitIsSet(vm->def->mem.hard_limit)) { if (qemuDomainSetMaxMemLock(vm, vm->def->mem.hard_limit << 10, &priv->preMigrationMemlock) < 0) return -1; /* Store the original memory locking limit */ qemuDomainSaveStatus(vm); } return qemuMonitorMigrateToHost(priv->mon, migrateFlags, spec->dest.host.protocol, spec->dest.host.name, spec->dest.host.port); case MIGRATION_DEST_SOCKET: qemuSecurityDomainSetPathLabel(driver, vm, spec->dest.socket.path, false); return qemuMonitorMigrateToSocket(priv->mon, migrateFlags, spec->dest.socket.path); case MIGRATION_DEST_CONNECT_HOST: case MIGRATION_DEST_CONNECT_SOCKET: /* handled above and transformed into MIGRATION_DEST_FD */ break; case MIGRATION_DEST_FD: if (spec->fwdType != MIGRATION_FWD_DIRECT) { if (!tunnelFd) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("tunnelFD argument is required for tunnelled " "migration")); VIR_FORCE_CLOSE(spec->dest.fd.local); VIR_FORCE_CLOSE(spec->dest.fd.qemu); return -1; } *tunnelFd = spec->dest.fd.local; spec->dest.fd.local = -1; } rc = qemuMonitorMigrateToFd(priv->mon, migrateFlags, spec->dest.fd.qemu); VIR_FORCE_CLOSE(spec->dest.fd.qemu); return rc; } virReportError(VIR_ERR_INTERNAL_ERROR, _("unexpected migration schema: %1$d"), spec->destType); return -1; } static bool qemuMigrationSrcIsCanceled(virDomainObj *vm) { virDomainJobData *jobData = vm->job->current; qemuDomainJobDataPrivate *priv = jobData->privateData; qemuMonitorMigrationStatus status = priv->stats.mig.status; switch (status) { case QEMU_MONITOR_MIGRATION_STATUS_COMPLETED: case QEMU_MONITOR_MIGRATION_STATUS_ERROR: case QEMU_MONITOR_MIGRATION_STATUS_CANCELLED: case QEMU_MONITOR_MIGRATION_STATUS_INACTIVE: VIR_DEBUG("QEMU migration status: %s; waiting finished", qemuMonitorMigrationStatusTypeToString(status)); return true; case QEMU_MONITOR_MIGRATION_STATUS_POSTCOPY: case QEMU_MONITOR_MIGRATION_STATUS_POSTCOPY_RECOVER: case QEMU_MONITOR_MIGRATION_STATUS_POSTCOPY_PAUSED: case QEMU_MONITOR_MIGRATION_STATUS_PRE_SWITCHOVER: case QEMU_MONITOR_MIGRATION_STATUS_DEVICE: case QEMU_MONITOR_MIGRATION_STATUS_SETUP: case QEMU_MONITOR_MIGRATION_STATUS_ACTIVE: case QEMU_MONITOR_MIGRATION_STATUS_CANCELLING: case QEMU_MONITOR_MIGRATION_STATUS_WAIT_UNPLUG: case QEMU_MONITOR_MIGRATION_STATUS_LAST: VIR_DEBUG("QEMU migration status: %s; still waiting", qemuMonitorMigrationStatusTypeToString(status)); break; } return false; } /** * Requests outgoing migration to be canceled and optionally waits for the * cancellation to complete. * * The thread (the caller itself in most cases) which is watching the migration * will do all the cleanup once migration is canceled. */ int qemuMigrationSrcCancel(virDomainObj *vm, virDomainAsyncJob asyncJob, bool wait) { qemuDomainObjPrivate *priv = vm->privateData; int rc; VIR_DEBUG("Cancelling outgoing migration of domain %s", vm->def->name); if (qemuDomainObjEnterMonitorAsync(vm, asyncJob) < 0) return -1; rc = qemuMonitorMigrateCancel(priv->mon); qemuDomainObjExitMonitor(vm); if (rc < 0) return -1; if (virDomainObjIsActive(vm) && wait) { VIR_DEBUG("Waiting for migration to be canceled"); while (!qemuMigrationSrcIsCanceled(vm)) { if (qemuDomainObjWait(vm) < 0) return -1; } } return 0; } static int qemuMigrationSrcRun(virQEMUDriver *driver, virDomainObj *vm, const char *persist_xml, const char *cookiein, int cookieinlen, char **cookieout, int *cookieoutlen, unsigned int flags, unsigned long resource, qemuMigrationSpec *spec, virConnectPtr dconn, const char *graphicsuri, size_t nmigrate_disks, const char **migrate_disks, qemuMigrationParams *migParams, const char *nbdURI) { int ret = -1; qemuDomainObjPrivate *priv = vm->privateData; g_autoptr(qemuMigrationCookie) mig = NULL; g_autofree char *tlsAlias = NULL; qemuMigrationIOThread *iothread = NULL; VIR_AUTOCLOSE fd = -1; unsigned long restore_max_bandwidth = priv->migMaxBandwidth; virErrorPtr orig_err = NULL; unsigned int cookieFlags = 0; bool abort_on_error = !!(flags & VIR_MIGRATE_ABORT_ON_ERROR); bool storageMigration = flags & (VIR_MIGRATE_NON_SHARED_DISK | VIR_MIGRATE_NON_SHARED_INC); bool cancel = false; unsigned int waitFlags; g_autoptr(virDomainDef) persistDef = NULL; int rc; if (resource > 0) priv->migMaxBandwidth = resource; VIR_DEBUG("driver=%p, vm=%p, cookiein=%s, cookieinlen=%d, " "cookieout=%p, cookieoutlen=%p, flags=0x%x, resource=%lu, " "spec=%p (dest=%d, fwd=%d), dconn=%p, graphicsuri=%s, " "nmigrate_disks=%zu, migrate_disks=%p", driver, vm, NULLSTR(cookiein), cookieinlen, cookieout, cookieoutlen, flags, resource, spec, spec->destType, spec->fwdType, dconn, NULLSTR(graphicsuri), nmigrate_disks, migrate_disks); if (storageMigration) storageMigration = qemuMigrationHasAnyStorageMigrationDisks(vm->def, migrate_disks, nmigrate_disks); if (storageMigration) { cookieFlags |= QEMU_MIGRATION_COOKIE_NBD; if (virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_MIGRATION_PARAM_BLOCK_BITMAP_MAPPING)) cookieFlags |= QEMU_MIGRATION_COOKIE_BLOCK_DIRTY_BITMAPS; } if (virLockManagerPluginUsesState(driver->lockManager) && !cookieout) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Migration with lock driver %1$s requires cookie support"), virLockManagerPluginGetName(driver->lockManager)); return -1; } priv->signalIOError = abort_on_error; if (flags & VIR_MIGRATE_PERSIST_DEST) { if (persist_xml) { if (!(persistDef = qemuMigrationAnyPrepareDef(driver, priv->qemuCaps, persist_xml, NULL, NULL))) goto error; } else { virDomainDef *def = vm->newDef ? vm->newDef : vm->def; if (!(persistDef = qemuDomainDefCopy(driver, priv->qemuCaps, def, VIR_DOMAIN_XML_SECURE | VIR_DOMAIN_XML_MIGRATABLE))) goto error; } } mig = qemuMigrationCookieParse(driver, vm, vm->def, priv->origname, priv->qemuCaps, cookiein, cookieinlen, cookieFlags | QEMU_MIGRATION_COOKIE_GRAPHICS | QEMU_MIGRATION_COOKIE_CAPS | QEMU_MIGRATION_COOKIE_BLOCK_DIRTY_BITMAPS); if (!mig) goto error; if (qemuMigrationSrcGraphicsRelocate(vm, mig, graphicsuri) < 0) VIR_WARN("unable to provide data for graphics client relocation"); if (mig->blockDirtyBitmaps && qemuMigrationSrcRunPrepareBlockDirtyBitmaps(vm, mig, migParams, flags) < 0) goto error; if (qemuMigrationParamsCheck(vm, VIR_ASYNC_JOB_MIGRATION_OUT, migParams, mig->caps->automatic) < 0) goto error; /* Save original migration parameters */ qemuDomainSaveStatus(vm); if (flags & VIR_MIGRATE_TLS) { const char *hostname = NULL; /* We need to add tls-hostname whenever QEMU itself does not * connect directly to the destination. */ if (spec->destType == MIGRATION_DEST_CONNECT_HOST || spec->destType == MIGRATION_DEST_FD) hostname = spec->dest.host.name; if (qemuMigrationParamsEnableTLS(driver, vm, false, VIR_ASYNC_JOB_MIGRATION_OUT, &tlsAlias, hostname, migParams) < 0) goto error; } else { if (qemuMigrationParamsDisableTLS(vm, migParams) < 0) goto error; } if (qemuMigrationParamsSetULL(migParams, QEMU_MIGRATION_PARAM_MAX_BANDWIDTH, priv->migMaxBandwidth * 1024 * 1024) < 0) goto error; if (qemuMigrationParamsApply(vm, VIR_ASYNC_JOB_MIGRATION_OUT, migParams, flags) < 0) goto error; if (flags & VIR_MIGRATE_ZEROCOPY) { /* Zero-copy requires pages in transfer to be locked in host memory. * Unfortunately, we have no reliable way of computing how many pages * will need to be locked at the same time. Thus we set the limit to * the whole guest memory and reset it back once migration is done. */ unsigned long long limit; if (virMemoryLimitIsSet(vm->def->mem.hard_limit)) limit = vm->def->mem.hard_limit; else limit = virDomainDefGetMemoryTotal(vm->def); if (qemuDomainSetMaxMemLock(vm, limit << 10, &priv->preMigrationMemlock) < 0) goto error; /* Store the original memory locking limit */ qemuDomainSaveStatus(vm); } if (storageMigration) { const char *host = ""; const char *tlsHostname = qemuMigrationParamsGetTLSHostname(migParams); if (!mig->nbd) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("migration of non-shared disks requested but NBD is not set up")); goto error; } if (spec->destType == MIGRATION_DEST_HOST || spec->destType == MIGRATION_DEST_CONNECT_HOST) { host = spec->dest.host.name; } if (qemuMigrationSrcNBDStorageCopy(driver, vm, mig, host, priv->migMaxBandwidth, nmigrate_disks, migrate_disks, dconn, tlsAlias, tlsHostname, nbdURI, flags) < 0) { goto error; } } if (qemuMigrationSetDBusVMState(driver, vm) < 0) goto error; /* Before EnterMonitor, since already qemuProcessStopCPUs does that */ if (!(flags & VIR_MIGRATE_LIVE) && virDomainObjGetState(vm, NULL) == VIR_DOMAIN_RUNNING) { if (qemuProcessStopCPUs(driver, vm, VIR_DOMAIN_PAUSED_MIGRATION, VIR_ASYNC_JOB_MIGRATION_OUT) < 0) goto error; } if (qemuDomainObjEnterMonitorAsync(vm, VIR_ASYNC_JOB_MIGRATION_OUT) < 0) goto error; if (vm->job->abortJob) { /* explicitly do this *after* we entered the monitor, * as this is a critical section so we are guaranteed * vm->job->abortJob will not change */ vm->job->current->status = VIR_DOMAIN_JOB_STATUS_CANCELED; virReportError(VIR_ERR_OPERATION_ABORTED, _("%1$s: %2$s"), virDomainAsyncJobTypeToString(vm->job->asyncJob), _("canceled by client")); goto exit_monitor; } rc = qemuMigrationSrcStart(vm, spec, 0, &fd); qemuDomainObjExitMonitor(vm); if (rc < 0) goto error; /* From this point onwards we *must* call cancel to abort the * migration on source if anything goes wrong */ cancel = true; if (spec->fwdType != MIGRATION_FWD_DIRECT) { if (!(iothread = qemuMigrationSrcStartTunnel(spec->fwd.stream, fd))) goto error; /* If we've created a tunnel, then the 'fd' will be closed in the * qemuMigrationIOFunc as data->sock. */ fd = -1; } waitFlags = QEMU_MIGRATION_COMPLETED_PRE_SWITCHOVER; if (abort_on_error) waitFlags |= QEMU_MIGRATION_COMPLETED_ABORT_ON_ERROR; if (mig->nbd) waitFlags |= QEMU_MIGRATION_COMPLETED_CHECK_STORAGE; if (flags & VIR_MIGRATE_POSTCOPY) waitFlags |= QEMU_MIGRATION_COMPLETED_POSTCOPY; rc = qemuMigrationSrcWaitForCompletion(vm, VIR_ASYNC_JOB_MIGRATION_OUT, dconn, waitFlags); if (rc == -2) goto error; if (rc == -1) { /* QEMU reported failed migration, nothing to cancel anymore */ cancel = false; goto error; } /* When migration completed, QEMU will have paused the CPUs for us. * Wait for the STOP event to be processed to release the lock state. */ while (virDomainObjGetState(vm, NULL) == VIR_DOMAIN_RUNNING) { priv->signalStop = true; rc = qemuDomainObjWait(vm); priv->signalStop = false; if (rc < 0) goto error; } if (mig->nbd && qemuMigrationSrcNBDCopyCancel(vm, false, VIR_ASYNC_JOB_MIGRATION_OUT, dconn) < 0) goto error; /* When migration was paused before serializing device state we need to * resume it now once we finished all block jobs and wait for the real * end of the migration. */ if (vm->job->current->status == VIR_DOMAIN_JOB_STATUS_PAUSED) { if (qemuMigrationSrcContinue(vm, QEMU_MONITOR_MIGRATION_STATUS_PRE_SWITCHOVER, VIR_ASYNC_JOB_MIGRATION_OUT) < 0) goto error; waitFlags ^= QEMU_MIGRATION_COMPLETED_PRE_SWITCHOVER; rc = qemuMigrationSrcWaitForCompletion(vm, VIR_ASYNC_JOB_MIGRATION_OUT, dconn, waitFlags); if (rc == -2) goto error; if (rc == -1) { /* QEMU reported failed migration, nothing to cancel anymore */ cancel = false; goto error; } } if (iothread) { qemuMigrationIOThread *io; io = g_steal_pointer(&iothread); if (qemuMigrationSrcStopTunnel(io, false) < 0) goto error; } if (vm->job->completed) { vm->job->completed->stopped = vm->job->current->stopped; qemuDomainJobDataUpdateTime(vm->job->completed); qemuDomainJobDataUpdateDowntime(vm->job->completed); ignore_value(virTimeMillisNow(&vm->job->completed->sent)); } cookieFlags |= QEMU_MIGRATION_COOKIE_NETWORK | QEMU_MIGRATION_COOKIE_STATS; if (qemuMigrationCookieAddPersistent(mig, &persistDef) < 0 || qemuMigrationCookieFormat(mig, driver, vm, QEMU_MIGRATION_SOURCE, cookieout, cookieoutlen, cookieFlags) < 0) { VIR_WARN("Unable to encode migration cookie"); } ret = 0; cleanup: priv->signalIOError = false; priv->migMaxBandwidth = restore_max_bandwidth; virErrorRestore(&orig_err); return ret; error: virErrorPreserveLast(&orig_err); if (virDomainObjIsActive(vm)) { int reason; virDomainState state = virDomainObjGetState(vm, &reason); if (state == VIR_DOMAIN_PAUSED && reason == VIR_DOMAIN_PAUSED_POSTCOPY) { VIR_DEBUG("Aborting failed post-copy migration as the destination is not running yet"); virDomainObjSetState(vm, state, VIR_DOMAIN_PAUSED_MIGRATION); } if (cancel && vm->job->current->status != VIR_DOMAIN_JOB_STATUS_HYPERVISOR_COMPLETED) qemuMigrationSrcCancel(vm, VIR_ASYNC_JOB_MIGRATION_OUT, true); /* cancel any outstanding NBD jobs */ if (mig && mig->nbd) qemuMigrationSrcNBDCopyCancel(vm, true, VIR_ASYNC_JOB_MIGRATION_OUT, dconn); qemuMigrationSrcCancelRemoveTempBitmaps(vm, VIR_ASYNC_JOB_MIGRATION_OUT); if (vm->job->current->status != VIR_DOMAIN_JOB_STATUS_CANCELED) vm->job->current->status = VIR_DOMAIN_JOB_STATUS_FAILED; } if (iothread) qemuMigrationSrcStopTunnel(iothread, true); goto cleanup; exit_monitor: qemuDomainObjExitMonitor(vm); goto error; } static int qemuMigrationSrcResume(virDomainObj *vm, qemuMigrationParams *migParams, const char *cookiein, int cookieinlen, char **cookieout, int *cookieoutlen, qemuMigrationSpec *spec, unsigned int flags) { qemuDomainObjPrivate *priv = vm->privateData; virQEMUDriver *driver = priv->driver; g_autoptr(qemuMigrationCookie) mig = NULL; int rc; VIR_DEBUG("vm=%p", vm); mig = qemuMigrationCookieParse(driver, vm, vm->def, priv->origname, priv->qemuCaps, cookiein, cookieinlen, QEMU_MIGRATION_COOKIE_CAPS); if (!mig) return -1; if (qemuMigrationParamsApply(vm, VIR_ASYNC_JOB_MIGRATION_OUT, migParams, flags) < 0) return -1; if (qemuDomainObjEnterMonitorAsync(vm, VIR_ASYNC_JOB_MIGRATION_OUT) < 0) return -1; rc = qemuMigrationSrcStart(vm, spec, QEMU_MONITOR_MIGRATE_RESUME, NULL); qemuDomainObjExitMonitor(vm); if (rc < 0) return -1; if (qemuMigrationCookieFormat(mig, driver, vm, QEMU_MIGRATION_SOURCE, cookieout, cookieoutlen, QEMU_MIGRATION_COOKIE_STATS) < 0) { VIR_WARN("Unable to encode migration cookie"); } return 0; } /* Perform migration using QEMU's native migrate support, * not encrypted obviously */ static int qemuMigrationSrcPerformNative(virQEMUDriver *driver, virDomainObj *vm, const char *persist_xml, const char *uri, const char *cookiein, int cookieinlen, char **cookieout, int *cookieoutlen, unsigned int flags, unsigned long resource, virConnectPtr dconn, const char *graphicsuri, size_t nmigrate_disks, const char **migrate_disks, qemuMigrationParams *migParams, const char *nbdURI) { qemuDomainObjPrivate *priv = vm->privateData; g_autoptr(virURI) uribits = NULL; int ret = -1; qemuMigrationSpec spec; VIR_DEBUG("driver=%p, vm=%p, uri=%s, cookiein=%s, cookieinlen=%d, " "cookieout=%p, cookieoutlen=%p, flags=0x%x, resource=%lu, " "graphicsuri=%s, nmigrate_disks=%zu migrate_disks=%p", driver, vm, uri, NULLSTR(cookiein), cookieinlen, cookieout, cookieoutlen, flags, resource, NULLSTR(graphicsuri), nmigrate_disks, migrate_disks); if (!(uribits = qemuMigrationAnyParseURI(uri, NULL))) return -1; if (uribits->scheme == NULL) { virReportError(VIR_ERR_INTERNAL_ERROR, _("missing scheme in migration URI: %1$s"), uri); return -1; } if (STREQ(uribits->scheme, "rdma")) { if (!virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_MIGRATE_RDMA)) { virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", _("outgoing RDMA migration is not supported " "with this QEMU binary")); return -1; } if (!virMemoryLimitIsSet(vm->def->mem.hard_limit)) { virReportError(VIR_ERR_OPERATION_INVALID, "%s", _("cannot start RDMA migration with no memory hard " "limit set")); return -1; } } if (STREQ(uribits->scheme, "unix")) { if ((flags & VIR_MIGRATE_TLS) && !(flags & VIR_MIGRATE_POSTCOPY_RESUME) && !qemuMigrationParamsTLSHostnameIsSet(migParams)) { virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", _("Explicit destination hostname is required " "for TLS migration over UNIX socket")); return -1; } if (flags & VIR_MIGRATE_PARALLEL) spec.destType = MIGRATION_DEST_SOCKET; else spec.destType = MIGRATION_DEST_CONNECT_SOCKET; spec.dest.socket.path = uribits->path; } else { /* RDMA and multi-fd migration requires QEMU to connect to the destination * itself. */ if (STREQ(uribits->scheme, "rdma") || (flags & VIR_MIGRATE_PARALLEL)) spec.destType = MIGRATION_DEST_HOST; else spec.destType = MIGRATION_DEST_CONNECT_HOST; spec.dest.host.protocol = uribits->scheme; spec.dest.host.name = uribits->server; spec.dest.host.port = uribits->port; } spec.fwdType = MIGRATION_FWD_DIRECT; if (flags & VIR_MIGRATE_POSTCOPY_RESUME) { ret = qemuMigrationSrcResume(vm, migParams, cookiein, cookieinlen, cookieout, cookieoutlen, &spec, flags); } else { ret = qemuMigrationSrcRun(driver, vm, persist_xml, cookiein, cookieinlen, cookieout, cookieoutlen, flags, resource, &spec, dconn, graphicsuri, nmigrate_disks, migrate_disks, migParams, nbdURI); } if (spec.destType == MIGRATION_DEST_FD) VIR_FORCE_CLOSE(spec.dest.fd.qemu); return ret; } static int qemuMigrationSrcPerformTunnel(virQEMUDriver *driver, virDomainObj *vm, virStreamPtr st, const char *persist_xml, const char *cookiein, int cookieinlen, char **cookieout, int *cookieoutlen, unsigned int flags, unsigned long resource, virConnectPtr dconn, const char *graphicsuri, size_t nmigrate_disks, const char **migrate_disks, qemuMigrationParams *migParams) { int ret = -1; qemuMigrationSpec spec; int fds[2] = { -1, -1 }; VIR_DEBUG("driver=%p, vm=%p, st=%p, cookiein=%s, cookieinlen=%d, " "cookieout=%p, cookieoutlen=%p, flags=0x%x, resource=%lu, " "graphicsuri=%s, nmigrate_disks=%zu, migrate_disks=%p", driver, vm, st, NULLSTR(cookiein), cookieinlen, cookieout, cookieoutlen, flags, resource, NULLSTR(graphicsuri), nmigrate_disks, migrate_disks); spec.fwdType = MIGRATION_FWD_STREAM; spec.fwd.stream = st; spec.destType = MIGRATION_DEST_FD; spec.dest.fd.qemu = -1; spec.dest.fd.local = -1; if (virPipe(fds) < 0) goto cleanup; spec.dest.fd.qemu = fds[1]; spec.dest.fd.local = fds[0]; if (spec.dest.fd.qemu == -1 || qemuSecuritySetImageFDLabel(driver->securityManager, vm->def, spec.dest.fd.qemu) < 0) { virReportSystemError(errno, "%s", _("cannot create pipe for tunnelled migration")); goto cleanup; } ret = qemuMigrationSrcRun(driver, vm, persist_xml, cookiein, cookieinlen, cookieout, cookieoutlen, flags, resource, &spec, dconn, graphicsuri, nmigrate_disks, migrate_disks, migParams, NULL); cleanup: VIR_FORCE_CLOSE(spec.dest.fd.qemu); VIR_FORCE_CLOSE(spec.dest.fd.local); return ret; } static int qemuMigrationSrcPerformResume(virQEMUDriver *driver, virConnectPtr conn, virDomainObj *vm, const char *uri, qemuMigrationParams *migParams, const char *cookiein, int cookieinlen, char **cookieout, int *cookieoutlen, unsigned int flags) { int ret; VIR_DEBUG("vm=%p, uri=%s", vm, uri); if (!qemuMigrationAnyCanResume(vm, VIR_ASYNC_JOB_MIGRATION_OUT, flags, QEMU_MIGRATION_PHASE_BEGIN_RESUME)) return -1; if (qemuMigrationJobStartPhase(vm, QEMU_MIGRATION_PHASE_PERFORM_RESUME) < 0) return -1; virCloseCallbacksDomainRemove(vm, NULL, qemuMigrationAnyConnectionClosed); qemuDomainCleanupRemove(vm, qemuProcessCleanupMigrationJob); ret = qemuMigrationSrcPerformNative(driver, vm, NULL, uri, cookiein, cookieinlen, cookieout, cookieoutlen, flags, 0, NULL, NULL, 0, NULL, migParams, NULL); virCloseCallbacksDomainAdd(vm, conn, qemuMigrationAnyConnectionClosed); if (ret < 0) ignore_value(qemuMigrationJobSetPhase(vm, QEMU_MIGRATION_PHASE_POSTCOPY_FAILED)); qemuMigrationJobContinue(vm, qemuProcessCleanupMigrationJob); return ret; } /* This is essentially a re-impl of virDomainMigrateVersion2 * from libvirt.c, but running in source libvirtd context, * instead of client app context & also adding in tunnel * handling */ static int qemuMigrationSrcPerformPeer2Peer2(virQEMUDriver *driver, virConnectPtr sconn, virConnectPtr dconn, virDomainObj *vm, const char *dconnuri, unsigned int flags, const char *dname, unsigned long resource, qemuMigrationParams *migParams) { virDomainPtr ddomain = NULL; char *uri_out = NULL; char *cookie = NULL; char *dom_xml = NULL; int cookielen = 0, ret; virErrorPtr orig_err = NULL; bool cancelled; virStreamPtr st = NULL; unsigned long destflags; VIR_DEBUG("driver=%p, sconn=%p, dconn=%p, vm=%p, dconnuri=%s, " "flags=0x%x, dname=%s, resource=%lu", driver, sconn, dconn, vm, NULLSTR(dconnuri), flags, NULLSTR(dname), resource); /* In version 2 of the protocol, the prepare step is slightly * different. We fetch the domain XML of the source domain * and pass it to Prepare2. */ if (!(dom_xml = qemuDomainFormatXML(driver, vm, QEMU_DOMAIN_FORMAT_LIVE_FLAGS | VIR_DOMAIN_XML_MIGRATABLE))) return -1; if (virDomainObjGetState(vm, NULL) == VIR_DOMAIN_PAUSED) flags |= VIR_MIGRATE_PAUSED; destflags = flags & ~(VIR_MIGRATE_ABORT_ON_ERROR | VIR_MIGRATE_AUTO_CONVERGE); VIR_DEBUG("Prepare2 %p", dconn); if (flags & VIR_MIGRATE_TUNNELLED) { /* * Tunnelled Migrate Version 2 does not support cookies * due to missing parameters in the prepareTunnel() API. */ if (!(st = virStreamNew(dconn, 0))) goto cleanup; qemuDomainObjEnterRemote(vm); ret = dconn->driver->domainMigratePrepareTunnel (dconn, st, destflags, dname, resource, dom_xml); if (qemuDomainObjExitRemote(vm, true) < 0) goto cleanup; } else { qemuDomainObjEnterRemote(vm); ret = dconn->driver->domainMigratePrepare2 (dconn, &cookie, &cookielen, NULL, &uri_out, destflags, dname, resource, dom_xml); if (qemuDomainObjExitRemote(vm, true) < 0) goto cleanup; } VIR_FREE(dom_xml); if (ret == -1) goto cleanup; if (!(flags & VIR_MIGRATE_TUNNELLED) && (uri_out == NULL)) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("domainMigratePrepare2 did not set uri")); cancelled = true; virErrorPreserveLast(&orig_err); goto finish; } /* Perform the migration. The driver isn't supposed to return * until the migration is complete. */ VIR_DEBUG("Perform %p", sconn); ignore_value(qemuMigrationJobStartPhase(vm, QEMU_MIGRATION_PHASE_PERFORM2)); if (flags & VIR_MIGRATE_TUNNELLED) ret = qemuMigrationSrcPerformTunnel(driver, vm, st, NULL, NULL, 0, NULL, NULL, flags, resource, dconn, NULL, 0, NULL, migParams); else ret = qemuMigrationSrcPerformNative(driver, vm, NULL, uri_out, cookie, cookielen, NULL, NULL, /* No out cookie with v2 migration */ flags, resource, dconn, NULL, 0, NULL, migParams, NULL); /* Perform failed. Make sure Finish doesn't overwrite the error */ if (ret < 0) virErrorPreserveLast(&orig_err); /* If Perform returns < 0, then we need to cancel the VM * startup on the destination */ cancelled = ret < 0; finish: /* In version 2 of the migration protocol, we pass the * status code from the sender to the destination host, * so it can do any cleanup if the migration failed. */ if (!dname) dname = vm->def->name; VIR_DEBUG("Finish2 %p ret=%d", dconn, ret); qemuDomainObjEnterRemote(vm); ddomain = dconn->driver->domainMigrateFinish2 (dconn, dname, cookie, cookielen, uri_out ? uri_out : dconnuri, destflags, cancelled); /* The domain is already gone at this point */ ignore_value(qemuDomainObjExitRemote(vm, false)); if (cancelled && ddomain) VIR_ERROR(_("finish step ignored that migration was cancelled")); cleanup: if (ddomain) { virObjectUnref(ddomain); ret = 0; } else { ret = -1; } virObjectUnref(st); virErrorRestore(&orig_err); VIR_FREE(uri_out); VIR_FREE(cookie); return ret; } /* This is essentially a re-impl of virDomainMigrateVersion3 * from libvirt.c, but running in source libvirtd context, * instead of client app context & also adding in tunnel * handling */ static int qemuMigrationSrcPerformPeer2Peer3(virQEMUDriver *driver, virConnectPtr sconn, virConnectPtr dconn, const char *dconnuri, virDomainObj *vm, const char *xmlin, const char *persist_xml, const char *dname, const char *uri, const char *graphicsuri, const char *listenAddress, size_t nmigrate_disks, const char **migrate_disks, int nbdPort, const char *nbdURI, qemuMigrationParams *migParams, unsigned long long bandwidth, bool useParams, unsigned int flags) { virDomainPtr ddomain = NULL; char *uri_out = NULL; char *cookiein = NULL; char *cookieout = NULL; g_autofree char *dom_xml = NULL; int cookieinlen = 0; int cookieoutlen = 0; int ret = -1; virErrorPtr orig_err = NULL; bool cancelled = true; virStreamPtr st = NULL; unsigned long destflags; virTypedParameterPtr params = NULL; int nparams = 0; int maxparams = 0; size_t i; bool offline = !!(flags & VIR_MIGRATE_OFFLINE); VIR_DEBUG("driver=%p, sconn=%p, dconn=%p, dconnuri=%s, vm=%p, xmlin=%s, " "dname=%s, uri=%s, graphicsuri=%s, listenAddress=%s, " "nmigrate_disks=%zu, migrate_disks=%p, nbdPort=%d, nbdURI=%s, " "bandwidth=%llu, useParams=%d, flags=0x%x", driver, sconn, dconn, NULLSTR(dconnuri), vm, NULLSTR(xmlin), NULLSTR(dname), NULLSTR(uri), NULLSTR(graphicsuri), NULLSTR(listenAddress), nmigrate_disks, migrate_disks, nbdPort, NULLSTR(nbdURI), bandwidth, useParams, flags); /* Unlike the virDomainMigrateVersion3 counterpart, we don't need * to worry about auto-setting the VIR_MIGRATE_CHANGE_PROTECTION * bit here, because we are already running inside the context of * a single job. */ if (flags & VIR_MIGRATE_POSTCOPY_RESUME) { dom_xml = qemuMigrationSrcBeginResume(vm, xmlin, &cookieout, &cookieoutlen, flags); } else { dom_xml = qemuMigrationSrcBeginPhase(driver, vm, xmlin, dname, &cookieout, &cookieoutlen, nmigrate_disks, migrate_disks, flags); } if (!dom_xml) goto cleanup; if (useParams) { if (virTypedParamsAddString(¶ms, &nparams, &maxparams, VIR_MIGRATE_PARAM_DEST_XML, dom_xml) < 0) goto cleanup; if (dname && virTypedParamsAddString(¶ms, &nparams, &maxparams, VIR_MIGRATE_PARAM_DEST_NAME, dname) < 0) goto cleanup; if (uri && virTypedParamsAddString(¶ms, &nparams, &maxparams, VIR_MIGRATE_PARAM_URI, uri) < 0) goto cleanup; if (bandwidth && virTypedParamsAddULLong(¶ms, &nparams, &maxparams, VIR_MIGRATE_PARAM_BANDWIDTH, bandwidth) < 0) goto cleanup; if (graphicsuri && virTypedParamsAddString(¶ms, &nparams, &maxparams, VIR_MIGRATE_PARAM_GRAPHICS_URI, graphicsuri) < 0) goto cleanup; if (listenAddress && virTypedParamsAddString(¶ms, &nparams, &maxparams, VIR_MIGRATE_PARAM_LISTEN_ADDRESS, listenAddress) < 0) goto cleanup; for (i = 0; i < nmigrate_disks; i++) if (virTypedParamsAddString(¶ms, &nparams, &maxparams, VIR_MIGRATE_PARAM_MIGRATE_DISKS, migrate_disks[i]) < 0) goto cleanup; if (nbdPort && virTypedParamsAddInt(¶ms, &nparams, &maxparams, VIR_MIGRATE_PARAM_DISKS_PORT, nbdPort) < 0) goto cleanup; if (nbdURI && virTypedParamsAddString(¶ms, &nparams, &maxparams, VIR_MIGRATE_PARAM_DISKS_URI, nbdURI) < 0) goto cleanup; if (qemuMigrationParamsDump(migParams, ¶ms, &nparams, &maxparams, &flags) < 0) goto cleanup; } if (!(flags & VIR_MIGRATE_POSTCOPY_RESUME) && virDomainObjGetState(vm, NULL) == VIR_DOMAIN_PAUSED) flags |= VIR_MIGRATE_PAUSED; destflags = flags & ~(VIR_MIGRATE_ABORT_ON_ERROR | VIR_MIGRATE_AUTO_CONVERGE); VIR_DEBUG("Prepare3 %p", dconn); cookiein = g_steal_pointer(&cookieout); cookieinlen = cookieoutlen; cookieoutlen = 0; if (flags & VIR_MIGRATE_TUNNELLED) { if (!(st = virStreamNew(dconn, 0))) goto cleanup; qemuDomainObjEnterRemote(vm); if (useParams) { ret = dconn->driver->domainMigratePrepareTunnel3Params (dconn, st, params, nparams, cookiein, cookieinlen, &cookieout, &cookieoutlen, destflags); } else { ret = dconn->driver->domainMigratePrepareTunnel3 (dconn, st, cookiein, cookieinlen, &cookieout, &cookieoutlen, destflags, dname, bandwidth, dom_xml); } if (qemuDomainObjExitRemote(vm, !offline) < 0) goto cleanup; } else { qemuDomainObjEnterRemote(vm); if (useParams) { ret = dconn->driver->domainMigratePrepare3Params (dconn, params, nparams, cookiein, cookieinlen, &cookieout, &cookieoutlen, &uri_out, destflags); } else { ret = dconn->driver->domainMigratePrepare3 (dconn, cookiein, cookieinlen, &cookieout, &cookieoutlen, uri, &uri_out, destflags, dname, bandwidth, dom_xml); } if (qemuDomainObjExitRemote(vm, !offline) < 0) goto cleanup; } VIR_FREE(dom_xml); if (ret == -1) goto cleanup; if (offline) { VIR_DEBUG("Offline migration, skipping Perform phase"); VIR_FREE(cookieout); cookieoutlen = 0; cancelled = false; goto finish; } if (uri_out) { uri = uri_out; if (useParams && virTypedParamsReplaceString(¶ms, &nparams, VIR_MIGRATE_PARAM_URI, uri_out) < 0) { virErrorPreserveLast(&orig_err); goto finish; } } else if (!uri && !(flags & VIR_MIGRATE_TUNNELLED)) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("domainMigratePrepare3 did not set uri")); virErrorPreserveLast(&orig_err); goto finish; } /* Perform the migration. The driver isn't supposed to return * until the migration is complete. The src VM should remain * running, but in paused state until the destination can * confirm migration completion. */ VIR_DEBUG("Perform3 %p uri=%s", sconn, NULLSTR(uri)); VIR_FREE(cookiein); cookiein = g_steal_pointer(&cookieout); cookieinlen = cookieoutlen; cookieoutlen = 0; if (flags & VIR_MIGRATE_POSTCOPY_RESUME) { ret = qemuMigrationSrcPerformResume(driver, dconn, vm, uri, migParams, cookiein, cookieinlen, &cookieout, &cookieoutlen, flags); } else { ignore_value(qemuMigrationJobSetPhase(vm, QEMU_MIGRATION_PHASE_PERFORM3)); if (flags & VIR_MIGRATE_TUNNELLED) { ret = qemuMigrationSrcPerformTunnel(driver, vm, st, persist_xml, cookiein, cookieinlen, &cookieout, &cookieoutlen, flags, bandwidth, dconn, graphicsuri, nmigrate_disks, migrate_disks, migParams); } else { ret = qemuMigrationSrcPerformNative(driver, vm, persist_xml, uri, cookiein, cookieinlen, &cookieout, &cookieoutlen, flags, bandwidth, dconn, graphicsuri, nmigrate_disks, migrate_disks, migParams, nbdURI); } if (ret == 0) ignore_value(qemuMigrationJobSetPhase(vm, QEMU_MIGRATION_PHASE_PERFORM3_DONE)); } /* Perform failed. Make sure Finish doesn't overwrite the error */ if (ret < 0) virErrorPreserveLast(&orig_err); /* If Perform returns < 0, then we need to cancel the VM * startup on the destination */ cancelled = ret < 0; finish: /* * The status code from the source is passed to the destination. * The dest can cleanup in the source indicated it failed to * send all migration data. Returns NULL for ddomain if * the dest was unable to complete migration. */ VIR_DEBUG("Finish3 %p ret=%d", dconn, ret); VIR_FREE(cookiein); cookiein = g_steal_pointer(&cookieout); cookieinlen = cookieoutlen; cookieoutlen = 0; if (useParams) { if (virTypedParamsGetString(params, nparams, VIR_MIGRATE_PARAM_DEST_NAME, NULL) <= 0 && virTypedParamsReplaceString(¶ms, &nparams, VIR_MIGRATE_PARAM_DEST_NAME, vm->def->name) < 0) { ddomain = NULL; } else { qemuDomainObjEnterRemote(vm); ddomain = dconn->driver->domainMigrateFinish3Params (dconn, params, nparams, cookiein, cookieinlen, &cookieout, &cookieoutlen, destflags, cancelled); if (qemuDomainObjExitRemote(vm, !offline) < 0) goto cleanup; } } else { if (!dname) dname = vm->def->name; qemuDomainObjEnterRemote(vm); ddomain = dconn->driver->domainMigrateFinish3 (dconn, dname, cookiein, cookieinlen, &cookieout, &cookieoutlen, dconnuri, uri, destflags, cancelled); if (qemuDomainObjExitRemote(vm, !offline) < 0) goto cleanup; } if (cancelled) { if (ddomain) { VIR_ERROR(_("finish step ignored that migration was cancelled")); } else { /* If Finish reported a useful error, use it instead of the * original "migration unexpectedly failed" error. * * This is ugly but we can't do better with the APIs we have. We * only replace the error if Finish was called with cancelled == 1 * and reported a real error (old libvirt would report an error * from RPC instead of MIGRATE_FINISH_OK), which only happens when * the domain died on destination. To further reduce a possibility * of false positives we also check that Perform returned * VIR_ERR_OPERATION_FAILED. */ if (orig_err && orig_err->domain == VIR_FROM_QEMU && orig_err->code == VIR_ERR_OPERATION_FAILED) { virErrorPtr err = virGetLastError(); if (err && err->domain == VIR_FROM_QEMU && err->code != VIR_ERR_MIGRATE_FINISH_OK) { g_clear_pointer(&orig_err, virFreeError); } } } } /* If ddomain is NULL, then we were unable to start * the guest on the target, and must restart on the * source. There is a small chance that the ddomain * is NULL due to an RPC failure, in which case * ddomain could in fact be running on the dest. * The lock manager plugins should take care of * safety in this scenario. */ cancelled = ddomain == NULL; /* If finish3 set an error, and we don't have an earlier * one we need to preserve it in case confirm3 overwrites */ if (!orig_err) virErrorPreserveLast(&orig_err); /* * If cancelled, then src VM will be restarted, else * it will be killed */ VIR_DEBUG("Confirm3 %p cancelled=%d vm=%p", sconn, cancelled, vm); VIR_FREE(cookiein); cookiein = g_steal_pointer(&cookieout); cookieinlen = cookieoutlen; cookieoutlen = 0; ret = qemuMigrationSrcConfirmPhase(driver, vm, cookiein, cookieinlen, flags, cancelled); /* If Confirm3 returns -1, there's nothing more we can * do, but fortunately worst case is that there is a * domain left in 'paused' state on source. */ if (ret < 0) VIR_WARN("Guest %s probably left in 'paused' state on source", vm->def->name); cleanup: if (ddomain) { virObjectUnref(ddomain); ret = 0; } else { ret = -1; } virObjectUnref(st); virErrorRestore(&orig_err); VIR_FREE(uri_out); VIR_FREE(cookiein); VIR_FREE(cookieout); virTypedParamsFree(params, nparams); return ret; } static void qemuMigrationSrcConnectionClosed(virConnectPtr conn, int reason, void *opaque) { virDomainObj *vm = opaque; VIR_DEBUG("conn=%p, reason=%d, vm=%s", conn, reason, vm->def->name); virDomainObjBroadcast(vm); } static int virConnectCredType[] = { VIR_CRED_AUTHNAME, VIR_CRED_PASSPHRASE, }; static virConnectAuth virConnectAuthConfig = { .credtype = virConnectCredType, .ncredtype = G_N_ELEMENTS(virConnectCredType), }; static int qemuMigrationSrcPerformPeer2Peer(virQEMUDriver *driver, virConnectPtr sconn, virDomainObj *vm, const char *xmlin, const char *persist_xml, const char *dconnuri, const char *uri, const char *graphicsuri, const char *listenAddress, size_t nmigrate_disks, const char **migrate_disks, int nbdPort, const char *nbdURI, qemuMigrationParams *migParams, unsigned int flags, const char *dname, unsigned long resource, bool *v3proto) { int ret = -1; g_autoptr(virConnect) dconn = NULL; int p2p; virErrorPtr orig_err = NULL; bool offline = !!(flags & VIR_MIGRATE_OFFLINE); int dstOffline = 0; g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver); int useParams; int rc; VIR_DEBUG("driver=%p, sconn=%p, vm=%p, xmlin=%s, dconnuri=%s, uri=%s, " "graphicsuri=%s, listenAddress=%s, nmigrate_disks=%zu, " "migrate_disks=%p, nbdPort=%d, nbdURI=%s, flags=0x%x, " "dname=%s, resource=%lu", driver, sconn, vm, NULLSTR(xmlin), NULLSTR(dconnuri), NULLSTR(uri), NULLSTR(graphicsuri), NULLSTR(listenAddress), nmigrate_disks, migrate_disks, nbdPort, NULLSTR(nbdURI), flags, NULLSTR(dname), resource); if (flags & VIR_MIGRATE_TUNNELLED && uri) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("migration URI is not supported by tunnelled " "migration")); goto cleanup; } if (flags & VIR_MIGRATE_TUNNELLED && listenAddress) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("listen address is not supported by tunnelled " "migration")); goto cleanup; } if (flags & VIR_MIGRATE_TUNNELLED && nbdPort) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("disk port address is not supported by tunnelled " "migration")); goto cleanup; } /* the order of operations is important here; we make sure the * destination side is completely setup before we touch the source */ qemuDomainObjEnterRemote(vm); dconn = virConnectOpenAuth(dconnuri, &virConnectAuthConfig, 0); if (qemuDomainObjExitRemote(vm, !offline) < 0) goto cleanup; if (dconn == NULL) { return -1; } if (virConnectSetKeepAlive(dconn, cfg->keepAliveInterval, cfg->keepAliveCount) < 0) goto cleanup; if (virConnectRegisterCloseCallback(dconn, qemuMigrationSrcConnectionClosed, vm, NULL) < 0) { goto cleanup; } qemuDomainObjEnterRemote(vm); p2p = VIR_DRV_SUPPORTS_FEATURE(dconn->driver, dconn, VIR_DRV_FEATURE_MIGRATION_P2P); if (p2p < 0) goto cleanup; /* v3proto reflects whether the caller used Perform3, but with * p2p migrate, regardless of whether Perform2 or Perform3 * were used, we decide protocol based on what target supports */ rc = VIR_DRV_SUPPORTS_FEATURE(dconn->driver, dconn, VIR_DRV_FEATURE_MIGRATION_V3); if (rc < 0) goto cleanup; *v3proto = !!rc; useParams = VIR_DRV_SUPPORTS_FEATURE(dconn->driver, dconn, VIR_DRV_FEATURE_MIGRATION_PARAMS); if (useParams < 0) goto cleanup; if (offline) { dstOffline = VIR_DRV_SUPPORTS_FEATURE(dconn->driver, dconn, VIR_DRV_FEATURE_MIGRATION_OFFLINE); if (dstOffline < 0) goto cleanup; } if (qemuDomainObjExitRemote(vm, !offline) < 0) goto cleanup; if (!p2p) { virReportError(VIR_ERR_OPERATION_FAILED, "%s", _("Destination libvirt does not support peer-to-peer migration protocol")); goto cleanup; } /* Only xmlin, dname, uri, and bandwidth parameters can be used with * old-style APIs. */ if (!useParams && (graphicsuri || listenAddress || nmigrate_disks)) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("Migration APIs with extensible parameters are not " "supported but extended parameters were passed")); goto cleanup; } if (offline && !dstOffline) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("offline migration is not supported by " "the destination host")); goto cleanup; } /* Change protection is only required on the source side (us), and * only for v3 migration when begin and perform are separate jobs. * But peer-2-peer is already a single job, and we still want to * talk to older destinations that would reject the flag. * Therefore it is safe to clear the bit here. */ flags &= ~VIR_MIGRATE_CHANGE_PROTECTION; if (*v3proto) { ret = qemuMigrationSrcPerformPeer2Peer3(driver, sconn, dconn, dconnuri, vm, xmlin, persist_xml, dname, uri, graphicsuri, listenAddress, nmigrate_disks, migrate_disks, nbdPort, nbdURI, migParams, resource, !!useParams, flags); } else { ret = qemuMigrationSrcPerformPeer2Peer2(driver, sconn, dconn, vm, dconnuri, flags, dname, resource, migParams); } cleanup: virErrorPreserveLast(&orig_err); if (dconn && virConnectIsAlive(dconn) == 1) { qemuDomainObjEnterRemote(vm); virConnectUnregisterCloseCallback(dconn, qemuMigrationSrcConnectionClosed); ignore_value(qemuDomainObjExitRemote(vm, false)); } virErrorRestore(&orig_err); return ret; } /* * This implements perform part of the migration protocol when migration job * does not need to be active across several APIs, i.e., peer2peer migration or * perform phase of v2 non-peer2peer migration. */ static int qemuMigrationSrcPerformJob(virQEMUDriver *driver, virConnectPtr conn, virDomainObj *vm, const char *xmlin, const char *persist_xml, const char *dconnuri, const char *uri, const char *graphicsuri, const char *listenAddress, size_t nmigrate_disks, const char **migrate_disks, int nbdPort, const char *nbdURI, qemuMigrationParams *migParams, const char *cookiein, int cookieinlen, char **cookieout, int *cookieoutlen, unsigned int flags, const char *dname, unsigned long resource, bool v3proto) { virObjectEvent *event = NULL; int ret = -1; virErrorPtr orig_err = NULL; g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver); qemuDomainObjPrivate *priv = vm->privateData; qemuDomainJobPrivate *jobPriv = vm->job->privateData; if (flags & VIR_MIGRATE_POSTCOPY_RESUME) { if (!qemuMigrationAnyCanResume(vm, VIR_ASYNC_JOB_MIGRATION_OUT, flags, QEMU_MIGRATION_PHASE_POSTCOPY_FAILED)) goto cleanup; if (qemuMigrationJobStartPhase(vm, QEMU_MIGRATION_PHASE_BEGIN_RESUME) < 0) goto cleanup; virCloseCallbacksDomainRemove(vm, NULL, qemuMigrationAnyConnectionClosed); qemuDomainCleanupRemove(vm, qemuProcessCleanupMigrationJob); } else { if (qemuMigrationJobStart(vm, VIR_ASYNC_JOB_MIGRATION_OUT, flags) < 0) goto cleanup; if (!(flags & VIR_MIGRATE_OFFLINE) && virDomainObjCheckActive(vm) < 0) goto endjob; if (!qemuMigrationSrcIsAllowed(vm, true, VIR_ASYNC_JOB_MIGRATION_OUT, flags)) goto endjob; if (!(flags & (VIR_MIGRATE_UNSAFE | VIR_MIGRATE_OFFLINE)) && !qemuMigrationSrcIsSafe(vm->def, priv->qemuCaps, nmigrate_disks, migrate_disks, flags)) goto endjob; qemuMigrationSrcStoreDomainState(vm); } if ((flags & (VIR_MIGRATE_TUNNELLED | VIR_MIGRATE_PEER2PEER))) { ret = qemuMigrationSrcPerformPeer2Peer(driver, conn, vm, xmlin, persist_xml, dconnuri, uri, graphicsuri, listenAddress, nmigrate_disks, migrate_disks, nbdPort, nbdURI, migParams, flags, dname, resource, &v3proto); } else { if (qemuMigrationJobStartPhase(vm, QEMU_MIGRATION_PHASE_PERFORM2) < 0) goto endjob; ret = qemuMigrationSrcPerformNative(driver, vm, persist_xml, uri, cookiein, cookieinlen, cookieout, cookieoutlen, flags, resource, NULL, NULL, 0, NULL, migParams, nbdURI); } if (ret < 0) goto endjob; /* * In v3 protocol, the source VM is not killed off until the * confirm step. */ if (!v3proto) { qemuProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_MIGRATED, VIR_ASYNC_JOB_MIGRATION_OUT, VIR_QEMU_PROCESS_STOP_MIGRATED); virDomainAuditStop(vm, "migrated"); event = virDomainEventLifecycleNewFromObj(vm, VIR_DOMAIN_EVENT_STOPPED, VIR_DOMAIN_EVENT_STOPPED_MIGRATED); } endjob: if (ret < 0) virErrorPreserveLast(&orig_err); if (virDomainObjIsFailedPostcopy(vm, vm->job)) { ignore_value(qemuMigrationJobSetPhase(vm, QEMU_MIGRATION_PHASE_POSTCOPY_FAILED)); qemuMigrationJobContinue(vm, qemuProcessCleanupMigrationJob); } else { /* v2 proto has no confirm phase so we need to reset migration parameters * here */ if (!v3proto && ret < 0) qemuMigrationParamsReset(vm, VIR_ASYNC_JOB_MIGRATION_OUT, jobPriv->migParams, vm->job->apiFlags); qemuMigrationSrcRestoreDomainState(driver, vm); qemuMigrationJobFinish(vm); } if (!virDomainObjIsActive(vm) && ret == 0) { if (flags & VIR_MIGRATE_UNDEFINE_SOURCE) { virDomainDeleteConfig(cfg->configDir, cfg->autostartDir, vm); vm->persistent = 0; } qemuDomainRemoveInactive(driver, vm, 0, true); } virErrorRestore(&orig_err); cleanup: virObjectEventStateQueue(driver->domainEventState, event); return ret; } /* * This implements perform phase of v3 migration protocol. */ static int qemuMigrationSrcPerformPhase(virQEMUDriver *driver, virConnectPtr conn, virDomainObj *vm, const char *persist_xml, const char *uri, const char *graphicsuri, size_t nmigrate_disks, const char **migrate_disks, qemuMigrationParams *migParams, const char *cookiein, int cookieinlen, char **cookieout, int *cookieoutlen, unsigned int flags, unsigned long resource, const char *nbdURI) { qemuDomainObjPrivate *priv = vm->privateData; qemuDomainJobPrivate *jobPriv = vm->job->privateData; int ret = -1; if (flags & VIR_MIGRATE_POSTCOPY_RESUME) { return qemuMigrationSrcPerformResume(driver, conn, vm, uri, migParams, cookiein, cookieinlen, cookieout, cookieoutlen, flags); } /* If we didn't start the job in the begin phase, start it now. */ if (!(flags & VIR_MIGRATE_CHANGE_PROTECTION)) { if (qemuMigrationJobStart(vm, VIR_ASYNC_JOB_MIGRATION_OUT, flags) < 0) return ret; } else if (!qemuMigrationJobIsActive(vm, VIR_ASYNC_JOB_MIGRATION_OUT)) { return ret; } if (qemuMigrationJobStartPhase(vm, QEMU_MIGRATION_PHASE_PERFORM3) < 0) goto cleanup; virCloseCallbacksDomainRemove(vm, NULL, qemuMigrationAnyConnectionClosed); if (qemuMigrationSrcPerformNative(driver, vm, persist_xml, uri, cookiein, cookieinlen, cookieout, cookieoutlen, flags, resource, NULL, graphicsuri, nmigrate_disks, migrate_disks, migParams, nbdURI) < 0) goto cleanup; virCloseCallbacksDomainAdd(vm, conn, qemuMigrationAnyConnectionClosed); ignore_value(qemuMigrationJobSetPhase(vm, QEMU_MIGRATION_PHASE_PERFORM3_DONE)); ret = 0; cleanup: if (ret < 0 && !virDomainObjIsFailedPostcopy(vm, vm->job)) { qemuMigrationSrcRestoreDomainState(driver, vm); qemuMigrationParamsReset(vm, VIR_ASYNC_JOB_MIGRATION_OUT, jobPriv->migParams, vm->job->apiFlags); qemuDomainSetMaxMemLock(vm, 0, &priv->preMigrationMemlock); qemuMigrationJobFinish(vm); } else { if (ret < 0) ignore_value(qemuMigrationJobSetPhase(vm, QEMU_MIGRATION_PHASE_POSTCOPY_FAILED)); qemuMigrationJobContinue(vm, qemuProcessCleanupMigrationJob); } if (!virDomainObjIsActive(vm)) qemuDomainRemoveInactive(driver, vm, 0, true); return ret; } int qemuMigrationSrcPerform(virQEMUDriver *driver, virConnectPtr conn, virDomainObj *vm, const char *xmlin, const char *persist_xml, const char *dconnuri, const char *uri, const char *graphicsuri, const char *listenAddress, size_t nmigrate_disks, const char **migrate_disks, int nbdPort, const char *nbdURI, qemuMigrationParams *migParams, const char *cookiein, int cookieinlen, char **cookieout, int *cookieoutlen, unsigned int flags, const char *dname, unsigned long resource, bool v3proto) { g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver); VIR_DEBUG("driver=%p, conn=%p, vm=%p, xmlin=%s, dconnuri=%s, " "uri=%s, graphicsuri=%s, listenAddress=%s, " "nmigrate_disks=%zu, migrate_disks=%p, nbdPort=%d, " "nbdURI=%s, " "cookiein=%s, cookieinlen=%d, cookieout=%p, cookieoutlen=%p, " "flags=0x%x, dname=%s, resource=%lu, v3proto=%d", driver, conn, vm, NULLSTR(xmlin), NULLSTR(dconnuri), NULLSTR(uri), NULLSTR(graphicsuri), NULLSTR(listenAddress), nmigrate_disks, migrate_disks, nbdPort, NULLSTR(nbdURI), NULLSTR(cookiein), cookieinlen, cookieout, cookieoutlen, flags, NULLSTR(dname), resource, v3proto); if (cfg->migrateTLSForce && !(flags & VIR_MIGRATE_TUNNELLED) && !(flags & VIR_MIGRATE_TLS)) { virReportError(VIR_ERR_OPERATION_INVALID, "%s", _("this libvirtd instance allows migration only with VIR_MIGRATE_TLS flag")); return -1; } if ((flags & (VIR_MIGRATE_TUNNELLED | VIR_MIGRATE_PEER2PEER))) { if (cookieinlen) { virReportError(VIR_ERR_OPERATION_INVALID, "%s", _("received unexpected cookie with P2P migration")); return -1; } return qemuMigrationSrcPerformJob(driver, conn, vm, xmlin, persist_xml, dconnuri, uri, graphicsuri, listenAddress, nmigrate_disks, migrate_disks, nbdPort, nbdURI, migParams, cookiein, cookieinlen, cookieout, cookieoutlen, flags, dname, resource, v3proto); } if (dconnuri) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Unexpected dconnuri parameter with non-peer2peer migration")); return -1; } if (v3proto) { return qemuMigrationSrcPerformPhase(driver, conn, vm, persist_xml, uri, graphicsuri, nmigrate_disks, migrate_disks, migParams, cookiein, cookieinlen, cookieout, cookieoutlen, flags, resource, nbdURI); } return qemuMigrationSrcPerformJob(driver, conn, vm, xmlin, persist_xml, NULL, uri, graphicsuri, listenAddress, nmigrate_disks, migrate_disks, nbdPort, nbdURI, migParams, cookiein, cookieinlen, cookieout, cookieoutlen, flags, dname, resource, v3proto); } static int qemuMigrationDstVPAssociatePortProfiles(virDomainDef *def) { size_t i; int last_good_net = -1; virDomainNetDef *net; for (i = 0; i < def->nnets; i++) { net = def->nets[i]; if (virDomainNetGetActualType(net) == VIR_DOMAIN_NET_TYPE_DIRECT) { if (virNetDevVPortProfileAssociate(net->ifname, virDomainNetGetActualVirtPortProfile(net), &net->mac, virDomainNetGetActualDirectDev(net), -1, def->uuid, VIR_NETDEV_VPORT_PROFILE_OP_MIGRATE_IN_FINISH, false) < 0) { virReportError(VIR_ERR_OPERATION_FAILED, _("Port profile Associate failed for %1$s"), net->ifname); goto err_exit; } last_good_net = i; VIR_DEBUG("Port profile Associate succeeded for %s", net->ifname); if (virNetDevMacVLanVPortProfileRegisterCallback(net->ifname, &net->mac, virDomainNetGetActualDirectDev(net), def->uuid, virDomainNetGetActualVirtPortProfile(net), VIR_NETDEV_VPORT_PROFILE_OP_CREATE)) goto err_exit; } } return 0; err_exit: for (i = 0; last_good_net != -1 && i <= last_good_net; i++) { net = def->nets[i]; if (virDomainNetGetActualType(net) == VIR_DOMAIN_NET_TYPE_DIRECT) { ignore_value(virNetDevVPortProfileDisassociate(net->ifname, virDomainNetGetActualVirtPortProfile(net), &net->mac, virDomainNetGetActualDirectDev(net), -1, VIR_NETDEV_VPORT_PROFILE_OP_MIGRATE_IN_FINISH)); } } return -1; } static int qemuMigrationDstPersist(virQEMUDriver *driver, virDomainObj *vm, qemuMigrationCookie *mig, bool ignoreSaveError) { g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver); qemuDomainObjPrivate *priv = vm->privateData; virDomainDef *vmdef; g_autoptr(virDomainDef) oldDef = NULL; unsigned int oldPersist = vm->persistent; virObjectEvent *event; virDomainEventDefinedDetailType eventDetail = VIR_DOMAIN_EVENT_DEFINED_UPDATED; vm->persistent = 1; oldDef = vm->newDef; vm->newDef = qemuMigrationCookieGetPersistent(mig); if (!(vmdef = virDomainObjGetPersistentDef(driver->xmlopt, vm, priv->qemuCaps))) goto error; if (!oldPersist) { eventDetail = VIR_DOMAIN_EVENT_DEFINED_ADDED; if (qemuDomainNamePathsCleanup(cfg, vmdef->name, false) < 0) goto error; } if (virDomainDefSave(vmdef, driver->xmlopt, cfg->configDir) < 0 && !ignoreSaveError) goto error; event = virDomainEventLifecycleNewFromObj(vm, VIR_DOMAIN_EVENT_DEFINED, eventDetail); virObjectEventStateQueue(driver->domainEventState, event); return 0; error: virDomainDefFree(vm->newDef); vm->persistent = oldPersist; vm->newDef = g_steal_pointer(&oldDef); return -1; } void qemuMigrationDstComplete(virQEMUDriver *driver, virDomainObj *vm, bool inPostCopy, virDomainAsyncJob asyncJob, virDomainJobObj *job) { qemuDomainObjPrivate *priv = vm->privateData; qemuDomainJobPrivate *jobPriv = job->privateData; virObjectEvent *event; if (inPostCopy) { if (virDomainObjGetState(vm, NULL) == VIR_DOMAIN_RUNNING) { virDomainObjSetState(vm, VIR_DOMAIN_RUNNING, VIR_DOMAIN_RUNNING_MIGRATED); } /* The only RESUME event during post-copy migration is triggered by * QEMU when the running domain moves from the source to the * destination host, but then the migration keeps running until all * modified memory is transferred from the source host. This will * result in VIR_DOMAIN_EVENT_RESUMED with RESUMED_POSTCOPY detail. * However, our API documentation says we need to fire another RESUMED * event at the very end of migration with RESUMED_MIGRATED detail. */ event = virDomainEventLifecycleNewFromObj(vm, VIR_DOMAIN_EVENT_RESUMED, VIR_DOMAIN_EVENT_RESUMED_MIGRATED); virObjectEventStateQueue(driver->domainEventState, event); } if (virDomainObjGetState(vm, NULL) == VIR_DOMAIN_PAUSED) { virDomainObjSetState(vm, VIR_DOMAIN_PAUSED, VIR_DOMAIN_PAUSED_USER); event = virDomainEventLifecycleNewFromObj(vm, VIR_DOMAIN_EVENT_SUSPENDED, VIR_DOMAIN_EVENT_SUSPENDED_PAUSED); virObjectEventStateQueue(driver->domainEventState, event); } qemuDomainSaveStatus(vm); /* Guest is successfully running, so cancel previous auto destroy. There's * nothing to remove when we are resuming post-copy migration. */ if (job->phase < QEMU_MIGRATION_PHASE_POSTCOPY_FAILED) virCloseCallbacksDomainRemove(vm, NULL, qemuProcessAutoDestroy); /* Remove completed stats for post-copy, everything but timing fields * is obsolete anyway. */ if (inPostCopy) g_clear_pointer(&job->completed, virDomainJobDataFree); qemuMigrationParamsReset(vm, asyncJob, jobPriv->migParams, job->apiFlags); virPortAllocatorRelease(priv->migrationPort); priv->migrationPort = 0; qemuDomainSetMaxMemLock(vm, 0, &priv->preMigrationMemlock); } static virDomainPtr qemuMigrationDstFinishOffline(virQEMUDriver *driver, virConnectPtr dconn, virDomainObj *vm, int cookie_flags, const char *cookiein, int cookieinlen, char **cookieout, int *cookieoutlen) { virDomainPtr dom = NULL; qemuDomainObjPrivate *priv = vm->privateData; g_autoptr(qemuMigrationCookie) mig = NULL; if (!(mig = qemuMigrationCookieParse(driver, vm, vm->def, priv->origname, priv->qemuCaps, cookiein, cookieinlen, cookie_flags))) return NULL; if (qemuMigrationDstPersist(driver, vm, mig, false) < 0) return NULL; dom = virGetDomain(dconn, vm->def->name, vm->def->uuid, -1); if (dom && qemuMigrationCookieFormat(mig, driver, vm, QEMU_MIGRATION_DESTINATION, cookieout, cookieoutlen, QEMU_MIGRATION_COOKIE_STATS) < 0) VIR_WARN("Unable to encode migration cookie"); return dom; } /* * Perform Finish phase of a fresh (i.e., not recovery) migration of an active * domain. */ static int qemuMigrationDstFinishFresh(virQEMUDriver *driver, virDomainObj *vm, qemuMigrationCookie *mig, unsigned int flags, bool v3proto, unsigned long long timeReceived, bool *doKill, bool *inPostCopy) { g_autoptr(virDomainJobData) jobData = NULL; if (qemuMigrationDstVPAssociatePortProfiles(vm->def) < 0) return -1; if (mig->network && qemuMigrationDstOPDRelocate(driver, vm, mig) < 0) VIR_WARN("unable to provide network data for relocation"); if (qemuMigrationDstStopNBDServer(vm, mig) < 0) return -1; if (qemuRefreshVirtioChannelState(driver, vm, VIR_ASYNC_JOB_MIGRATION_IN) < 0) return -1; if (qemuConnectAgent(driver, vm) < 0) return -1; if (flags & VIR_MIGRATE_PERSIST_DEST) { if (qemuMigrationDstPersist(driver, vm, mig, !v3proto) < 0) { /* Hmpf. Migration was successful, but making it persistent * was not. If we report successful, then when this domain * shuts down, management tools are in for a surprise. On the * other hand, if we report failure, then the management tools * might try to restart the domain on the source side, even * though the domain is actually running on the destination. * Pretend success and hope that this is a rare situation and * management tools are smart. * * However, in v3 protocol, the source VM is still available * to restart during confirm() step, so we kill it off now. */ if (v3proto) return -1; } } /* We need to wait for QEMU to process all data sent by the source * before starting guest CPUs. */ if (qemuMigrationDstWaitForCompletion(vm, VIR_ASYNC_JOB_MIGRATION_IN, !!(flags & VIR_MIGRATE_POSTCOPY)) < 0) { /* There's not much we can do for v2 protocol since the * original domain on the source host is already gone. */ if (v3proto) return -1; } /* Now that the state data was transferred we can refresh the actual state * of the devices */ if (qemuProcessRefreshState(driver, vm, VIR_ASYNC_JOB_MIGRATION_IN) < 0) { /* Similarly to the case above v2 protocol will not be able to recover * from this. Let's ignore this and perhaps stuff will not break. */ if (v3proto) return -1; } if (vm->job->current->status == VIR_DOMAIN_JOB_STATUS_POSTCOPY) *inPostCopy = true; if (!(flags & VIR_MIGRATE_PAUSED)) { virDomainRunningReason runningReason = VIR_DOMAIN_RUNNING_MIGRATED; if (*inPostCopy) runningReason = VIR_DOMAIN_RUNNING_POSTCOPY; if (qemuProcessStartCPUs(driver, vm, runningReason, VIR_ASYNC_JOB_MIGRATION_IN) < 0) { if (virGetLastErrorCode() == VIR_ERR_OK) virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("resume operation failed")); /* * In v3 protocol, the source VM is still available to * restart during confirm() step, so we kill it off * now. * In v2 protocol, the source is dead, so we leave * target in paused state, in case admin can fix * things up. */ if (v3proto) return -1; } if (*inPostCopy) *doKill = false; } if (mig->jobData) { jobData = g_steal_pointer(&mig->jobData); if (jobData->sent && timeReceived) { jobData->timeDelta = timeReceived - jobData->sent; jobData->received = timeReceived; jobData->timeDeltaSet = true; } qemuDomainJobDataUpdateTime(jobData); qemuDomainJobDataUpdateDowntime(jobData); } if (*inPostCopy && qemuMigrationDstWaitForCompletion(vm, VIR_ASYNC_JOB_MIGRATION_IN, false) < 0) { return -1; } if (jobData) { vm->job->completed = g_steal_pointer(&jobData); vm->job->completed->status = VIR_DOMAIN_JOB_STATUS_COMPLETED; qemuDomainJobSetStatsType(vm->job->completed, QEMU_DOMAIN_JOB_STATS_TYPE_MIGRATION); } return 0; } static int qemuMigrationDstFinishResume(virDomainObj *vm) { VIR_DEBUG("vm=%p", vm); if (qemuMigrationDstWaitForCompletion(vm, VIR_ASYNC_JOB_MIGRATION_IN, false) < 0) { return -1; } return 0; } static virDomainPtr qemuMigrationDstFinishActive(virQEMUDriver *driver, virConnectPtr dconn, virDomainObj *vm, int cookie_flags, const char *cookiein, int cookieinlen, char **cookieout, int *cookieoutlen, unsigned int flags, int retcode, bool v3proto, unsigned long long timeReceived, bool *finishJob) { virErrorPtr orig_err = NULL; virDomainPtr dom = NULL; g_autoptr(qemuMigrationCookie) mig = NULL; qemuDomainObjPrivate *priv = vm->privateData; qemuDomainJobPrivate *jobPriv = vm->job->privateData; virObjectEvent *event; bool inPostCopy = false; bool doKill = vm->job->phase != QEMU_MIGRATION_PHASE_FINISH_RESUME; int rc; VIR_DEBUG("vm=%p, flags=0x%x, retcode=%d", vm, flags, retcode); if (!(mig = qemuMigrationCookieParse(driver, vm, vm->def, priv->origname, priv->qemuCaps, cookiein, cookieinlen, cookie_flags))) goto error; if (retcode != 0) { /* Check for a possible error on the monitor in case Finish was called * earlier than monitor EOF handler got a chance to process the error */ qemuDomainCheckMonitor(vm, VIR_ASYNC_JOB_MIGRATION_IN); goto error; } if (!virDomainObjIsActive(vm)) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("guest unexpectedly quit")); qemuMigrationDstErrorReport(driver, vm->def->name); goto error; } if (flags & VIR_MIGRATE_POSTCOPY_RESUME) { rc = qemuMigrationDstFinishResume(vm); inPostCopy = true; } else { rc = qemuMigrationDstFinishFresh(driver, vm, mig, flags, v3proto, timeReceived, &doKill, &inPostCopy); } if (rc < 0 || !(dom = virGetDomain(dconn, vm->def->name, vm->def->uuid, vm->def->id))) goto error; if (qemuMigrationCookieFormat(mig, driver, vm, QEMU_MIGRATION_DESTINATION, cookieout, cookieoutlen, QEMU_MIGRATION_COOKIE_STATS) < 0) VIR_WARN("Unable to encode migration cookie"); qemuMigrationDstComplete(driver, vm, inPostCopy, VIR_ASYNC_JOB_MIGRATION_IN, vm->job); return dom; error: /* Need to save the current error, in case shutting down the process * overwrites it. */ virErrorPreserveLast(&orig_err); if (virDomainObjIsActive(vm)) { if (doKill) { qemuProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_FAILED, VIR_ASYNC_JOB_MIGRATION_IN, VIR_QEMU_PROCESS_STOP_MIGRATED); virDomainAuditStop(vm, "failed"); event = virDomainEventLifecycleNewFromObj(vm, VIR_DOMAIN_EVENT_STOPPED, VIR_DOMAIN_EVENT_STOPPED_FAILED); virObjectEventStateQueue(driver->domainEventState, event); } else { qemuMigrationDstPostcopyFailed(vm); } } if (virDomainObjIsFailedPostcopy(vm, vm->job)) { ignore_value(qemuMigrationJobSetPhase(vm, QEMU_MIGRATION_PHASE_POSTCOPY_FAILED)); virCloseCallbacksDomainRemove(vm, NULL, qemuProcessAutoDestroy); *finishJob = false; } else { qemuMigrationParamsReset(vm, VIR_ASYNC_JOB_MIGRATION_IN, jobPriv->migParams, vm->job->apiFlags); } if (!virDomainObjIsActive(vm)) qemuDomainRemoveInactive(driver, vm, VIR_DOMAIN_UNDEFINE_TPM, false); virErrorRestore(&orig_err); return NULL; } virDomainPtr qemuMigrationDstFinish(virQEMUDriver *driver, virConnectPtr dconn, virDomainObj *vm, const char *cookiein, int cookieinlen, char **cookieout, int *cookieoutlen, unsigned int flags, int retcode, bool v3proto) { virDomainPtr dom = NULL; int cookie_flags = 0; qemuDomainObjPrivate *priv = vm->privateData; unsigned short port; unsigned long long timeReceived = 0; int phase = v3proto ? QEMU_MIGRATION_PHASE_FINISH3 : QEMU_MIGRATION_PHASE_FINISH2; VIR_DEBUG("driver=%p, dconn=%p, vm=%p, cookiein=%s, cookieinlen=%d, " "cookieout=%p, cookieoutlen=%p, flags=0x%x, retcode=%d", driver, dconn, vm, NULLSTR(cookiein), cookieinlen, cookieout, cookieoutlen, flags, retcode); port = priv->migrationPort; priv->migrationPort = 0; if (!qemuMigrationJobIsActive(vm, VIR_ASYNC_JOB_MIGRATION_IN)) { qemuMigrationDstErrorReport(driver, vm->def->name); goto cleanup; } if (flags & VIR_MIGRATE_POSTCOPY_RESUME) { if (!qemuMigrationAnyCanResume(vm, VIR_ASYNC_JOB_MIGRATION_IN, flags, QEMU_MIGRATION_PHASE_PREPARE_RESUME)) goto cleanup; phase = QEMU_MIGRATION_PHASE_FINISH_RESUME; } ignore_value(virTimeMillisNow(&timeReceived)); if (qemuMigrationJobStartPhase(vm, phase) < 0) goto cleanup; if (flags & VIR_MIGRATE_POSTCOPY_RESUME) { virCloseCallbacksDomainRemove(vm, NULL, qemuMigrationAnyConnectionClosed); qemuDomainCleanupRemove(vm, qemuProcessCleanupMigrationJob); } else { qemuDomainCleanupRemove(vm, qemuMigrationDstPrepareCleanup); } g_clear_pointer(&vm->job->completed, virDomainJobDataFree); cookie_flags = QEMU_MIGRATION_COOKIE_NETWORK | QEMU_MIGRATION_COOKIE_STATS | QEMU_MIGRATION_COOKIE_NBD; /* Some older versions of libvirt always send persistent XML in the cookie * even though VIR_MIGRATE_PERSIST_DEST was not used. */ cookie_flags |= QEMU_MIGRATION_COOKIE_PERSISTENT; if (flags & VIR_MIGRATE_OFFLINE) { if (retcode == 0) { dom = qemuMigrationDstFinishOffline(driver, dconn, vm, cookie_flags, cookiein, cookieinlen, cookieout, cookieoutlen); } qemuMigrationJobFinish(vm); } else { bool finishJob = true; dom = qemuMigrationDstFinishActive(driver, dconn, vm, cookie_flags, cookiein, cookieinlen, cookieout, cookieoutlen, flags, retcode, v3proto, timeReceived, &finishJob); if (finishJob) qemuMigrationJobFinish(vm); else qemuMigrationJobContinue(vm, qemuProcessCleanupMigrationJob); } cleanup: virPortAllocatorRelease(port); if (priv->mon) qemuMonitorSetDomainLog(priv->mon, NULL, NULL, NULL); VIR_FREE(priv->origname); virDomainObjEndAPI(&vm); /* Set a special error if Finish is expected to return NULL as a result of * successful call with retcode != 0 */ if (retcode != 0 && !dom && virGetLastErrorCode() == VIR_ERR_OK) virReportError(VIR_ERR_MIGRATE_FINISH_OK, NULL); return dom; } void qemuMigrationProcessUnattended(virQEMUDriver *driver, virDomainObj *vm, virDomainAsyncJob job, qemuMonitorMigrationStatus status) { qemuMigrationJobPhase phase; if (!qemuMigrationJobIsActive(vm, job) || status != QEMU_MONITOR_MIGRATION_STATUS_COMPLETED) return; VIR_DEBUG("Unattended %s migration of domain %s successfully finished", job == VIR_ASYNC_JOB_MIGRATION_IN ? "incoming" : "outgoing", vm->def->name); if (job == VIR_ASYNC_JOB_MIGRATION_IN) phase = QEMU_MIGRATION_PHASE_FINISH_RESUME; else phase = QEMU_MIGRATION_PHASE_CONFIRM_RESUME; if (qemuMigrationJobStartPhase(vm, phase) < 0) return; if (job == VIR_ASYNC_JOB_MIGRATION_IN) qemuMigrationDstComplete(driver, vm, true, job, vm->job); else qemuMigrationSrcComplete(driver, vm, job); qemuMigrationJobFinish(vm); if (!virDomainObjIsActive(vm)) qemuDomainRemoveInactive(driver, vm, 0, false); } /* Helper function called while vm is active. */ int qemuMigrationSrcToFile(virQEMUDriver *driver, virDomainObj *vm, int fd, virCommand *compressor, virDomainAsyncJob asyncJob) { qemuDomainObjPrivate *priv = vm->privateData; int rc; int ret = -1; int pipeFD[2] = { -1, -1 }; unsigned long saveMigBandwidth = priv->migMaxBandwidth; char *errbuf = NULL; virErrorPtr orig_err = NULL; g_autoptr(qemuMigrationParams) migParams = NULL; if (qemuMigrationSetDBusVMState(driver, vm) < 0) return -1; /* Increase migration bandwidth to unlimited since target is a file. * Failure to change migration speed is not fatal. */ if (!(migParams = qemuMigrationParamsNew())) return -1; if (qemuMigrationParamsSetULL(migParams, QEMU_MIGRATION_PARAM_MAX_BANDWIDTH, QEMU_DOMAIN_MIG_BANDWIDTH_MAX * 1024 * 1024) < 0) return -1; if (qemuMigrationParamsApply(vm, asyncJob, migParams, 0) < 0) return -1; priv->migMaxBandwidth = QEMU_DOMAIN_MIG_BANDWIDTH_MAX; if (!virDomainObjIsActive(vm)) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("guest unexpectedly quit")); /* nothing to tear down */ return -1; } if (compressor && virPipe(pipeFD) < 0) return -1; /* All right! We can use fd migration, which means that qemu * doesn't have to open() the file, so while we still have to * grant SELinux access, we can do it on fd and avoid cleanup * later, as well as skip futzing with cgroup. */ if (qemuSecuritySetImageFDLabel(driver->securityManager, vm->def, compressor ? pipeFD[1] : fd) < 0) goto cleanup; if (qemuDomainObjEnterMonitorAsync(vm, asyncJob) < 0) goto cleanup; if (!compressor) { rc = qemuMonitorMigrateToFd(priv->mon, 0, fd); } else { virCommandSetInputFD(compressor, pipeFD[0]); virCommandSetOutputFD(compressor, &fd); virCommandSetErrorBuffer(compressor, &errbuf); virCommandDoAsyncIO(compressor); if (virSetCloseExec(pipeFD[1]) < 0) { virReportSystemError(errno, "%s", _("Unable to set cloexec flag")); qemuDomainObjExitMonitor(vm); goto cleanup; } if (virCommandRunAsync(compressor, NULL) < 0) { qemuDomainObjExitMonitor(vm); goto cleanup; } rc = qemuMonitorMigrateToFd(priv->mon, 0, pipeFD[1]); if (VIR_CLOSE(pipeFD[0]) < 0 || VIR_CLOSE(pipeFD[1]) < 0) VIR_WARN("failed to close intermediate pipe"); } qemuDomainObjExitMonitor(vm); if (rc < 0) goto cleanup; rc = qemuMigrationSrcWaitForCompletion(vm, asyncJob, NULL, 0); if (rc < 0) { if (rc == -2) { virErrorPreserveLast(&orig_err); virCommandAbort(compressor); if (virDomainObjIsActive(vm)) qemuMigrationSrcCancel(vm, asyncJob, true); } goto cleanup; } if (compressor && virCommandWait(compressor, NULL) < 0) goto cleanup; qemuDomainEventEmitJobCompleted(driver, vm); ret = 0; cleanup: if (ret < 0 && !orig_err) virErrorPreserveLast(&orig_err); /* Restore max migration bandwidth */ if (virDomainObjIsActive(vm)) { if (qemuMigrationParamsSetULL(migParams, QEMU_MIGRATION_PARAM_MAX_BANDWIDTH, saveMigBandwidth * 1024 * 1024) == 0) ignore_value(qemuMigrationParamsApply(vm, asyncJob, migParams, 0)); priv->migMaxBandwidth = saveMigBandwidth; } VIR_FORCE_CLOSE(pipeFD[0]); VIR_FORCE_CLOSE(pipeFD[1]); if (errbuf) { VIR_DEBUG("Compression binary stderr: %s", NULLSTR(errbuf)); VIR_FREE(errbuf); } virErrorRestore(&orig_err); return ret; } /** * This function is supposed to be used only to while reconnecting to a domain * with an active migration job. */ int qemuMigrationSrcCancelUnattended(virDomainObj *vm, virDomainJobObj *oldJob) { bool storage = false; size_t i; VIR_DEBUG("Canceling unfinished outgoing migration of domain %s", vm->def->name); /* Make sure MIGRATION event handler can store the current migration state * in the job. */ if (!vm->job->current) { qemuDomainObjRestoreAsyncJob(vm, VIR_ASYNC_JOB_MIGRATION_OUT, oldJob->phase, oldJob->asyncStarted, VIR_DOMAIN_JOB_OPERATION_MIGRATION_OUT, QEMU_DOMAIN_JOB_STATS_TYPE_MIGRATION, VIR_DOMAIN_JOB_STATUS_FAILED, VIR_JOB_NONE); } /* We're inside a MODIFY job and the restored MIGRATION_OUT async job is * used only for processing migration events from QEMU. Thus we don't want * to start a nested job for talking to QEMU. */ qemuMigrationSrcCancel(vm, VIR_ASYNC_JOB_NONE, true); virDomainObjEndAsyncJob(vm); for (i = 0; i < vm->def->ndisks; i++) { virDomainDiskDef *disk = vm->def->disks[i]; qemuDomainDiskPrivate *diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk); qemuBlockJobData *job; if (!(job = qemuBlockJobDiskGetJob(disk)) || !qemuBlockJobIsRunning(job)) diskPriv->migrating = false; if (diskPriv->migrating) { qemuBlockJobSyncBegin(job); storage = true; } virObjectUnref(job); } if (storage && qemuMigrationSrcNBDCopyCancel(vm, true, VIR_ASYNC_JOB_NONE, NULL) < 0) return -1; if (qemuMigrationSrcCancelRemoveTempBitmaps(vm, VIR_ASYNC_JOB_NONE) < 0) return -1; return 0; } static void qemuMigrationDstErrorFree(void *data) { virErrorPtr err = data; virFreeError(err); } int qemuMigrationDstErrorInit(virQEMUDriver *driver) { driver->migrationErrors = virHashAtomicNew(qemuMigrationDstErrorFree); if (driver->migrationErrors) return 0; return -1; } /** * This function consumes @err; the caller should consider the @err pointer * invalid after calling this function. */ void qemuMigrationDstErrorSave(virQEMUDriver *driver, const char *name, virErrorPtr err) { if (!err) return; VIR_DEBUG("Saving incoming migration error for domain %s: %s", name, err->message); if (virHashAtomicUpdate(driver->migrationErrors, name, err) < 0) { VIR_WARN("Failed to save migration error for domain '%s'", name); virFreeError(err); } } void qemuMigrationDstErrorReport(virQEMUDriver *driver, const char *name) { virErrorPtr err; if (!(err = virHashAtomicSteal(driver->migrationErrors, name))) return; VIR_DEBUG("Restoring saved incoming migration error for domain %s: %s", name, err->message); virErrorRestore(&err); } int qemuMigrationSrcFetchMirrorStats(virDomainObj *vm, virDomainAsyncJob asyncJob, virDomainJobData *jobData) { size_t i; qemuDomainObjPrivate *priv = vm->privateData; qemuDomainJobDataPrivate *privJob = jobData->privateData; bool nbd = false; g_autoptr(GHashTable) blockinfo = NULL; qemuDomainMirrorStats *stats = &privJob->mirrorStats; for (i = 0; i < vm->def->ndisks; i++) { virDomainDiskDef *disk = vm->def->disks[i]; if (QEMU_DOMAIN_DISK_PRIVATE(disk)->migrating) { nbd = true; break; } } if (!nbd) return 0; if (qemuDomainObjEnterMonitorAsync(vm, asyncJob) < 0) return -1; blockinfo = qemuMonitorGetAllBlockJobInfo(priv->mon, false); qemuDomainObjExitMonitor(vm); if (!blockinfo) return -1; memset(stats, 0, sizeof(*stats)); for (i = 0; i < vm->def->ndisks; i++) { virDomainDiskDef *disk = vm->def->disks[i]; qemuDomainDiskPrivate *diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk); qemuMonitorBlockJobInfo *data; if (!diskPriv->migrating || !(data = virHashLookup(blockinfo, disk->info.alias))) continue; stats->transferred += data->cur; stats->total += data->end; } return 0; }