diff --git a/src/qemu/qemu_capabilities.c b/src/qemu/qemu_capabilities.c index 3b08ef8596..be86014cfe 100644 --- a/src/qemu/qemu_capabilities.c +++ b/src/qemu/qemu_capabilities.c @@ -183,6 +183,7 @@ VIR_ENUM_IMPL(qemuCaps, QEMU_CAPS_LAST, "reboot-timeout", /* 110 */ "dump-guest-core", + "seamless-migration", ); struct _qemuCaps { @@ -1149,6 +1150,8 @@ qemuCapsComputeCmdFlags(const char *help, } if (strstr(help, "-spice")) qemuCapsSet(caps, QEMU_CAPS_SPICE); + if (strstr(help, "seamless-migration=")) + qemuCapsSet(caps, QEMU_CAPS_SEAMLESS_MIGRATION); if (strstr(help, "boot=on")) qemuCapsSet(caps, QEMU_CAPS_DRIVE_BOOT); if (strstr(help, "serial=s")) diff --git a/src/qemu/qemu_capabilities.h b/src/qemu/qemu_capabilities.h index 485c2974df..9e4543aede 100644 --- a/src/qemu/qemu_capabilities.h +++ b/src/qemu/qemu_capabilities.h @@ -147,6 +147,7 @@ enum qemuCapsFlags { QEMU_CAPS_SECCOMP_SANDBOX = 109, /* -sandbox */ QEMU_CAPS_REBOOT_TIMEOUT = 110, /* -boot reboot-timeout */ QEMU_CAPS_DUMP_GUEST_CORE = 111, /* dump-guest-core-parameter */ + QEMU_CAPS_SEAMLESS_MIGRATION = 112, /* seamless-migration for SPICE */ QEMU_CAPS_LAST, /* this must always be the last item */ }; diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c index e7bb88e697..4e14ae3fb4 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -6150,6 +6150,14 @@ qemuBuildCommandLine(virConnectPtr conn, if (def->graphics[0]->data.spice.copypaste == VIR_DOMAIN_GRAPHICS_SPICE_CLIPBOARD_COPYPASTE_NO) virBufferAddLit(&opt, ",disable-copy-paste"); + if (qemuCapsGet(caps, QEMU_CAPS_SEAMLESS_MIGRATION)) { + /* If qemu supports seamless migration turn it + * unconditionally on. If migration destination + * doesn't support it, it fallbacks to previous + * migration algorithm silently. */ + virBufferAddLit(&opt, ",seamless-migration=on"); + } + virCommandAddArg(cmd, "-spice"); virCommandAddArgBuffer(cmd, &opt); if (def->graphics[0]->data.spice.keymap) diff --git a/src/qemu/qemu_migration.c b/src/qemu/qemu_migration.c index 8e8587573c..db69a0aa4a 100644 --- a/src/qemu/qemu_migration.c +++ b/src/qemu/qemu_migration.c @@ -896,10 +896,19 @@ qemuMigrationUpdateJobStatus(struct qemud_driver *driver, qemuDomainObjPrivatePtr priv = vm->privateData; int ret; int status; + bool wait_for_spice = false; + bool spice_migrated = false; unsigned long long memProcessed; unsigned long long memRemaining; unsigned long long memTotal; + /* If guest uses SPICE and supports seamles_migration we have to hold up + * migration finish until SPICE server transfers its data */ + if (vm->def->ngraphics == 1 && + vm->def->graphics[0]->type == VIR_DOMAIN_GRAPHICS_TYPE_SPICE && + qemuCapsGet(priv->caps, QEMU_CAPS_SEAMLESS_MIGRATION)) + wait_for_spice = true; + ret = qemuDomainObjEnterMonitorAsync(driver, vm, asyncJob); if (ret < 0) { /* Guest already exited; nothing further to update. */ @@ -910,6 +919,13 @@ qemuMigrationUpdateJobStatus(struct qemud_driver *driver, &memProcessed, &memRemaining, &memTotal); + + /* If qemu says migrated, check spice */ + if (wait_for_spice && (ret == 0) && + (status == QEMU_MONITOR_MIGRATION_STATUS_COMPLETED)) + ret = qemuMonitorGetSpiceMigrationStatus(priv->mon, + &spice_migrated); + qemuDomainObjExitMonitorWithDriver(driver, vm); if (ret < 0 || virTimeMillisNow(&priv->job.info.timeElapsed) < 0) { @@ -939,7 +955,8 @@ qemuMigrationUpdateJobStatus(struct qemud_driver *driver, break; case QEMU_MONITOR_MIGRATION_STATUS_COMPLETED: - priv->job.info.type = VIR_DOMAIN_JOB_COMPLETED; + if ((wait_for_spice && spice_migrated) || (!wait_for_spice)) + priv->job.info.type = VIR_DOMAIN_JOB_COMPLETED; ret = 0; break; diff --git a/src/qemu/qemu_monitor.c b/src/qemu/qemu_monitor.c index 2796a613dc..58a9f945cf 100644 --- a/src/qemu/qemu_monitor.c +++ b/src/qemu/qemu_monitor.c @@ -1828,6 +1828,30 @@ int qemuMonitorGetMigrationStatus(qemuMonitorPtr mon, } +int qemuMonitorGetSpiceMigrationStatus(qemuMonitorPtr mon, + bool *spice_migrated) +{ + int ret; + VIR_DEBUG("mon=%p", mon); + + if (!mon) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("monitor must not be NULL")); + return -1; + } + + if (mon->json) { + ret = qemuMonitorJSONGetSpiceMigrationStatus(mon, spice_migrated); + } else { + virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", + _("JSON monitor is required")); + return -1; + } + + return ret; +} + + int qemuMonitorMigrateToFd(qemuMonitorPtr mon, unsigned int flags, int fd) diff --git a/src/qemu/qemu_monitor.h b/src/qemu/qemu_monitor.h index 6b4eb6f1e3..3455ab9c25 100644 --- a/src/qemu/qemu_monitor.h +++ b/src/qemu/qemu_monitor.h @@ -343,6 +343,8 @@ int qemuMonitorGetMigrationStatus(qemuMonitorPtr mon, unsigned long long *transferred, unsigned long long *remaining, unsigned long long *total); +int qemuMonitorGetSpiceMigrationStatus(qemuMonitorPtr mon, + bool *spice_migrated); typedef enum { QEMU_MONITOR_MIGRATE_BACKGROUND = 1 << 0, diff --git a/src/qemu/qemu_monitor_json.c b/src/qemu/qemu_monitor_json.c index f372199e36..c55ee2bb09 100644 --- a/src/qemu/qemu_monitor_json.c +++ b/src/qemu/qemu_monitor_json.c @@ -2490,6 +2490,58 @@ int qemuMonitorJSONGetMigrationStatus(qemuMonitorPtr mon, } +static int +qemuMonitorJSONSpiceGetMigrationStatusReply(virJSONValuePtr reply, + bool *spice_migrated) +{ + virJSONValuePtr ret; + const char *migrated_str; + + if (!(ret = virJSONValueObjectGet(reply, "return"))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("query-spice reply was missing return data")); + return -1; + } + + if (!(migrated_str = virJSONValueObjectGetString(ret, "migrated"))) { + /* Deliberately don't report error here as we are + * probably dealing with older qemu which doesn't + * report this yet. Pretend spice is migrated. */ + *spice_migrated = true; + } else { + *spice_migrated = STREQ(migrated_str, "true"); + } + + return 0; +} + + +int qemuMonitorJSONGetSpiceMigrationStatus(qemuMonitorPtr mon, + bool *spice_migrated) +{ + int ret; + virJSONValuePtr cmd = qemuMonitorJSONMakeCommand("query-spice", + NULL); + virJSONValuePtr reply = NULL; + + if (!cmd) + return -1; + + ret = qemuMonitorJSONCommand(mon, cmd, &reply); + + if (ret == 0) + ret = qemuMonitorJSONCheckError(cmd, reply); + + if (ret == 0) + ret = qemuMonitorJSONSpiceGetMigrationStatusReply(reply, + spice_migrated); + + virJSONValueFree(cmd); + virJSONValueFree(reply); + return ret; +} + + int qemuMonitorJSONMigrate(qemuMonitorPtr mon, unsigned int flags, const char *uri) diff --git a/src/qemu/qemu_monitor_json.h b/src/qemu/qemu_monitor_json.h index e531eb182e..751b81f948 100644 --- a/src/qemu/qemu_monitor_json.h +++ b/src/qemu/qemu_monitor_json.h @@ -134,6 +134,9 @@ int qemuMonitorJSONGetMigrationStatus(qemuMonitorPtr mon, int qemuMonitorJSONMigrate(qemuMonitorPtr mon, unsigned int flags, const char *uri); +int qemuMonitorJSONGetSpiceMigrationStatus(qemuMonitorPtr mon, + bool *spice_migrated); + int qemuMonitorJSONMigrateCancel(qemuMonitorPtr mon);