diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c index b517e1a6e2..7ac1faf9f5 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -3222,6 +3222,13 @@ qemuBuildCommandLine(virConnectPtr conn, def->onReboot != VIR_DOMAIN_LIFECYCLE_RESTART) virCommandAddArg(cmd, "-no-reboot"); + /* If JSON monitor is enabled, we can receive an event + * when QEMU stops. If we use no-shutdown, then we can + * watch for this event and do a soft/warm reboot. + */ + if (monitor_json) + virCommandAddArg(cmd, "-no-shutdown"); + if (!(def->features & (1 << VIR_DOMAIN_FEATURE_ACPI))) virCommandAddArg(cmd, "-no-acpi"); diff --git a/src/qemu/qemu_domain.h b/src/qemu/qemu_domain.h index 3d041fc074..f282df2780 100644 --- a/src/qemu/qemu_domain.h +++ b/src/qemu/qemu_domain.h @@ -91,6 +91,8 @@ struct _qemuDomainObjPrivate { virBitmapPtr qemuCaps; char *lockState; + + bool fakeReboot; }; struct qemuDomainWatchdogEvent diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 2f416c8949..18fa4e0cd8 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -1428,7 +1428,7 @@ cleanup: } -static int qemudDomainShutdown(virDomainPtr dom) { +static int qemuDomainShutdown(virDomainPtr dom) { struct qemud_driver *driver = dom->conn->privateData; virDomainObjPtr vm; int ret = -1; @@ -1460,6 +1460,8 @@ static int qemudDomainShutdown(virDomainPtr dom) { ret = qemuMonitorSystemPowerdown(priv->mon); qemuDomainObjExitMonitor(vm); + priv->fakeReboot = false; + endjob: if (qemuDomainObjEndJob(vm) == 0) vm = NULL; @@ -1471,11 +1473,68 @@ cleanup: } +static int qemuDomainReboot(virDomainPtr dom, unsigned int flags) { + struct qemud_driver *driver = dom->conn->privateData; + virDomainObjPtr vm; + int ret = -1; + qemuDomainObjPrivatePtr priv; + + virCheckFlags(0, -1); + + qemuDriverLock(driver); + vm = virDomainFindByUUID(&driver->domains, dom->uuid); + qemuDriverUnlock(driver); + + if (!vm) { + char uuidstr[VIR_UUID_STRING_BUFLEN]; + virUUIDFormat(dom->uuid, uuidstr); + qemuReportError(VIR_ERR_NO_DOMAIN, + _("no domain with matching uuid '%s'"), uuidstr); + goto cleanup; + } + priv = vm->privateData; + +#if HAVE_YAJL + if (qemuCapsGet(priv->qemuCaps, QEMU_CAPS_MONITOR_JSON)) { + if (qemuDomainObjBeginJob(vm) < 0) + goto cleanup; + + if (!virDomainObjIsActive(vm)) { + qemuReportError(VIR_ERR_OPERATION_INVALID, + "%s", _("domain is not running")); + goto endjob; + } + + qemuDomainObjEnterMonitor(vm); + ret = qemuMonitorSystemPowerdown(priv->mon); + qemuDomainObjExitMonitor(vm); + + priv->fakeReboot = true; + + endjob: + if (qemuDomainObjEndJob(vm) == 0) + vm = NULL; + } else { +#endif + qemuReportError(VIR_ERR_NO_SUPPORT, "%s", + _("Reboot is not supported without the JSON monitor")); +#if HAVE_YAJL + } +#endif + +cleanup: + if (vm) + virDomainObjUnlock(vm); + return ret; +} + + static int qemudDomainDestroy(virDomainPtr dom) { struct qemud_driver *driver = dom->conn->privateData; virDomainObjPtr vm; int ret = -1; virDomainEventPtr event = NULL; + qemuDomainObjPrivatePtr priv; qemuDriverLock(driver); vm = virDomainFindByUUID(&driver->domains, dom->uuid); @@ -1487,6 +1546,9 @@ static int qemudDomainDestroy(virDomainPtr dom) { goto cleanup; } + priv = vm->privateData; + priv->fakeReboot = false; + /* Although qemuProcessStop does this already, there may * be an outstanding job active. We want to make sure we * can kill the process even if a job is active. Killing @@ -8336,7 +8398,8 @@ static virDriver qemuDriver = { .domainLookupByName = qemudDomainLookupByName, /* 0.2.0 */ .domainSuspend = qemudDomainSuspend, /* 0.2.0 */ .domainResume = qemudDomainResume, /* 0.2.0 */ - .domainShutdown = qemudDomainShutdown, /* 0.2.0 */ + .domainShutdown = qemuDomainShutdown, /* 0.2.0 */ + .domainReboot = qemuDomainReboot, /* 0.9.3 */ .domainDestroy = qemudDomainDestroy, /* 0.2.0 */ .domainGetOSType = qemudDomainGetOSType, /* 0.2.2 */ .domainGetMaxMemory = qemudDomainGetMaxMemory, /* 0.4.2 */ diff --git a/src/qemu/qemu_monitor.c b/src/qemu/qemu_monitor.c index 89a3f642a2..2aa5c9a977 100644 --- a/src/qemu/qemu_monitor.c +++ b/src/qemu/qemu_monitor.c @@ -1095,6 +1095,25 @@ int qemuMonitorSystemPowerdown(qemuMonitorPtr mon) } +int qemuMonitorSystemReset(qemuMonitorPtr mon) +{ + int ret; + VIR_DEBUG("mon=%p", mon); + + if (!mon) { + qemuReportError(VIR_ERR_INVALID_ARG, "%s", + _("monitor must not be NULL")); + return -1; + } + + if (mon->json) + ret = qemuMonitorJSONSystemReset(mon); + else + ret = qemuMonitorTextSystemReset(mon); + return ret; +} + + int qemuMonitorGetCPUInfo(qemuMonitorPtr mon, int **pids) { diff --git a/src/qemu/qemu_monitor.h b/src/qemu/qemu_monitor.h index 3bb026900e..ae74a3ceb6 100644 --- a/src/qemu/qemu_monitor.h +++ b/src/qemu/qemu_monitor.h @@ -194,6 +194,7 @@ int qemuMonitorStartCPUs(qemuMonitorPtr mon, int qemuMonitorStopCPUs(qemuMonitorPtr mon); int qemuMonitorGetStatus(qemuMonitorPtr mon, bool *running); +int qemuMonitorSystemReset(qemuMonitorPtr mon); int qemuMonitorSystemPowerdown(qemuMonitorPtr mon); int qemuMonitorGetCPUInfo(qemuMonitorPtr mon, diff --git a/src/qemu/qemu_monitor_json.c b/src/qemu/qemu_monitor_json.c index 56ec65bb18..6cc6ea7861 100644 --- a/src/qemu/qemu_monitor_json.c +++ b/src/qemu/qemu_monitor_json.c @@ -947,6 +947,25 @@ int qemuMonitorJSONSystemPowerdown(qemuMonitorPtr mon) } +int qemuMonitorJSONSystemReset(qemuMonitorPtr mon) +{ + int ret; + virJSONValuePtr cmd = qemuMonitorJSONMakeCommand("system_reset", NULL); + virJSONValuePtr reply = NULL; + if (!cmd) + return -1; + + ret = qemuMonitorJSONCommand(mon, cmd, &reply); + + if (ret == 0) + ret = qemuMonitorJSONCheckError(cmd, reply); + + virJSONValueFree(cmd); + virJSONValueFree(reply); + return ret; +} + + /* * [ { "CPU": 0, "current": true, "halted": false, "pc": 3227107138 }, * { "CPU": 1, "current": false, "halted": true, "pc": 7108165 } ] diff --git a/src/qemu/qemu_monitor_json.h b/src/qemu/qemu_monitor_json.h index 393d8fcaa9..a72bf7c6b4 100644 --- a/src/qemu/qemu_monitor_json.h +++ b/src/qemu/qemu_monitor_json.h @@ -49,6 +49,7 @@ int qemuMonitorJSONStopCPUs(qemuMonitorPtr mon); int qemuMonitorJSONGetStatus(qemuMonitorPtr mon, bool *running); int qemuMonitorJSONSystemPowerdown(qemuMonitorPtr mon); +int qemuMonitorJSONSystemReset(qemuMonitorPtr mon); int qemuMonitorJSONGetCPUInfo(qemuMonitorPtr mon, int **pids); diff --git a/src/qemu/qemu_monitor_text.c b/src/qemu/qemu_monitor_text.c index a16ea91e1e..67333aaee1 100644 --- a/src/qemu/qemu_monitor_text.c +++ b/src/qemu/qemu_monitor_text.c @@ -417,6 +417,19 @@ int qemuMonitorTextSystemPowerdown(qemuMonitorPtr mon) { } +int qemuMonitorTextSystemReset(qemuMonitorPtr mon) { + char *info; + + if (qemuMonitorHMPCommand(mon, "system_reset", &info) < 0) { + qemuReportError(VIR_ERR_OPERATION_FAILED, + "%s", _("system reset operation failed")); + return -1; + } + VIR_FREE(info); + return 0; +} + + int qemuMonitorTextGetCPUInfo(qemuMonitorPtr mon, int **pids) { diff --git a/src/qemu/qemu_monitor_text.h b/src/qemu/qemu_monitor_text.h index 4fa5064620..7536557423 100644 --- a/src/qemu/qemu_monitor_text.h +++ b/src/qemu/qemu_monitor_text.h @@ -46,6 +46,7 @@ int qemuMonitorTextStopCPUs(qemuMonitorPtr mon); int qemuMonitorTextGetStatus(qemuMonitorPtr mon, bool *running); int qemuMonitorTextSystemPowerdown(qemuMonitorPtr mon); +int qemuMonitorTextSystemReset(qemuMonitorPtr mon); int qemuMonitorTextGetCPUInfo(qemuMonitorPtr mon, int **pids); diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index b4c732db4c..f8f95e2db0 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -353,13 +353,103 @@ qemuProcessHandleReset(qemuMonitorPtr mon ATTRIBUTE_UNUSED, } +/* + * Since we have the '-no-shutdown' flag set, the + * QEMU process will currently have guest OS shutdown + * and the CPUS stopped. To fake the reboot, we thus + * want todo a reset of the virtual hardware, followed + * by restart of the CPUs. This should result in the + * guest OS booting up again + */ +static void +qemuProcessFakeReboot(void *opaque) +{ + struct qemud_driver *driver = qemu_driver; + virDomainObjPtr vm = opaque; + qemuDomainObjPrivatePtr priv = vm->privateData; + virDomainEventPtr event = NULL; + int ret = -1; + VIR_DEBUG("vm=%p", vm); + qemuDriverLock(driver); + virDomainObjLock(vm); + if (qemuDomainObjBeginJob(vm) < 0) + goto cleanup; + + if (!virDomainObjIsActive(vm)) { + qemuReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("guest unexpectedly quit")); + goto endjob; + } + + qemuDomainObjEnterMonitorWithDriver(driver, vm); + if (qemuMonitorSystemReset(priv->mon) < 0) { + qemuDomainObjExitMonitorWithDriver(driver, vm); + goto endjob; + } + qemuDomainObjExitMonitorWithDriver(driver, vm); + + if (!virDomainObjIsActive(vm)) { + qemuReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("guest unexpectedly quit")); + goto endjob; + } + + if (qemuProcessStartCPUs(driver, vm, NULL, + VIR_DOMAIN_RUNNING_BOOTED) < 0) { + if (virGetLastError() == NULL) + qemuReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("resume operation failed")); + goto endjob; + } + event = virDomainEventNewFromObj(vm, + VIR_DOMAIN_EVENT_RESUMED, + VIR_DOMAIN_EVENT_RESUMED_UNPAUSED); + + ret = 0; + +endjob: + if (qemuDomainObjEndJob(vm) == 0) + vm = NULL; + +cleanup: + if (vm) { + if (ret == -1) + qemuProcessKill(vm); + if (virDomainObjUnref(vm) > 0) + virDomainObjUnlock(vm); + } + if (event) + qemuDomainEventQueue(driver, event); + qemuDriverUnlock(driver); +} + + static int qemuProcessHandleShutdown(qemuMonitorPtr mon ATTRIBUTE_UNUSED, virDomainObjPtr vm) { + qemuDomainObjPrivatePtr priv = vm->privateData; + VIR_DEBUG("vm=%p", vm); + virDomainObjLock(vm); - ((qemuDomainObjPrivatePtr) vm->privateData)->gotShutdown = true; - virDomainObjUnlock(vm); + priv->gotShutdown = true; + if (priv->fakeReboot) { + virDomainObjRef(vm); + virThread th; + if (virThreadCreate(&th, + false, + qemuProcessFakeReboot, + vm) < 0) { + VIR_ERROR("Failed to create reboot thread, killing domain"); + qemuProcessKill(vm); + if (virDomainObjUnref(vm) == 0) + vm = NULL; + } + } else { + qemuProcessKill(vm); + } + if (vm) + virDomainObjUnlock(vm); return 0; } @@ -2087,6 +2177,11 @@ qemuProcessPrepareMonitorChr(struct qemud_driver *driver, } +/* + * Precondition: Both driver and vm must be locked, + * and a job must be active. This method will call + * {Enter,Exit}MonitorWithDriver + */ int qemuProcessStartCPUs(struct qemud_driver *driver, virDomainObjPtr vm, virConnectPtr conn, virDomainRunningReason reason) @@ -2349,6 +2444,7 @@ int qemuProcessStart(virConnectPtr conn, goto cleanup; vm->def->id = driver->nextvmid++; + priv->fakeReboot = false; /* Run an early hook to set-up missing devices */ if (virHookPresent(VIR_HOOK_DRIVER_QEMU)) { diff --git a/tests/qemuxml2argvdata/qemuxml2argv-monitor-json.args b/tests/qemuxml2argvdata/qemuxml2argv-monitor-json.args new file mode 100644 index 0000000000..8d8e43ebd1 --- /dev/null +++ b/tests/qemuxml2argvdata/qemuxml2argv-monitor-json.args @@ -0,0 +1,5 @@ +LC_ALL=C PATH=/bin HOME=/home/test USER=test LOGNAME=test /usr/bin/qemu -S -M \ +pc -m 214 -smp 1 -nographic -nodefconfig -nodefaults -chardev socket,\ +id=charmonitor,path=/tmp/test-monitor,server,nowait -mon chardev=charmonitor,\ +id=monitor,mode=control -no-shutdown -no-acpi -boot c -hda /dev/hda1 -usb -device \ +virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3 diff --git a/tests/qemuxml2argvdata/qemuxml2argv-monitor-json.xml b/tests/qemuxml2argvdata/qemuxml2argv-monitor-json.xml new file mode 100644 index 0000000000..1901715de7 --- /dev/null +++ b/tests/qemuxml2argvdata/qemuxml2argv-monitor-json.xml @@ -0,0 +1,24 @@ + + encryptdisk + 496898a6-e6ff-f7c8-5dc2-3cf410945ee9 + 219100 + 219100 + 1 + + hvm + + + + destroy + restart + destroy + + /usr/bin/qemu + + + + + + + + diff --git a/tests/qemuxml2argvtest.c b/tests/qemuxml2argvtest.c index 782664a4e3..ec1f4b5833 100644 --- a/tests/qemuxml2argvtest.c +++ b/tests/qemuxml2argvtest.c @@ -27,6 +27,7 @@ static int testCompareXMLToArgvFiles(const char *xml, virBitmapPtr extraFlags, const char *migrateFrom, int migrateFd, + bool json, bool expectError) { char *expectargv = NULL; @@ -116,7 +117,7 @@ static int testCompareXMLToArgvFiles(const char *xml, } if (!(cmd = qemuBuildCommandLine(conn, &driver, - vmdef, &monitor_chr, false, extraFlags, + vmdef, &monitor_chr, json, extraFlags, migrateFrom, migrateFd, NULL, VIR_VM_OP_NO_OP))) goto fail; @@ -168,6 +169,7 @@ struct testInfo { virBitmapPtr extraFlags; const char *migrateFrom; int migrateFd; + bool json; bool expectError; }; @@ -186,8 +188,8 @@ testCompareXMLToArgvHelper(const void *data) goto cleanup; result = testCompareXMLToArgvFiles(xml, args, info->extraFlags, - info->migrateFrom, info->migrateFd, - info->expectError); + info->migrateFrom, info->migrateFd, + info->json, info->expectError); cleanup: free(xml); @@ -202,6 +204,7 @@ mymain(void) { int ret = 0; char *map = NULL; + bool json = false; abs_top_srcdir = getenv("abs_top_srcdir"); if (!abs_top_srcdir) @@ -229,7 +232,7 @@ mymain(void) # define DO_TEST_FULL(name, migrateFrom, migrateFd, expectError, ...) \ do { \ struct testInfo info = { \ - name, NULL, migrateFrom, migrateFd, expectError \ + name, NULL, migrateFrom, migrateFd, json, expectError \ }; \ if (!(info.extraFlags = qemuCapsNew())) \ return EXIT_FAILURE; \ @@ -529,6 +532,11 @@ mymain(void) QEMU_CAPS_DRIVE, QEMU_CAPS_DEVICE, QEMU_CAPS_NODEFCONFIG, QEMU_CAPS_PCI_MULTIFUNCTION); + json = true; + DO_TEST("monitor-json", false, QEMU_CAPS_DEVICE, + QEMU_CAPS_CHARDEV, QEMU_CAPS_MONITOR_JSON, QEMU_CAPS_NODEFCONFIG); + json = false; + free(driver.stateDir); virCapabilitiesFree(driver.caps); free(map);