"reboot-timeout", /* 110 */
"dump-guest-core",
+ "seamless-migration",
);
struct _qemuCaps {
}
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"))
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 */
};
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)
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. */
&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) {
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;
}
+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)
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,
}
+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)
int qemuMonitorJSONMigrate(qemuMonitorPtr mon,
unsigned int flags,
const char *uri);
+int qemuMonitorJSONGetSpiceMigrationStatus(qemuMonitorPtr mon,
+ bool *spice_migrated);
+
int qemuMonitorJSONMigrateCancel(qemuMonitorPtr mon);