mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2025-02-01 01:15:19 +00:00
Support reboots with the QEMU driver
For controlled shutdown we issue a 'system_powerdown' command to the QEMU monitor. This triggers an ACPI event which (most) guest OS wire up to a controlled shutdown. There is no equiv ACPI event to trigger a controlled reboot. This patch attempts to fake a reboot. - In qemuDomainObjPrivatePtr we have a bool fakeReboot flag. - The virDomainReboot method sets this flag and then triggers a normal 'system_powerdown'. - The QEMU process is started with '-no-shutdown' so that the guest CPUs pause when it powers off the guest - When we receive the 'POWEROFF' event from QEMU JSON monitor if fakeReboot is not set we invoke the qemuProcessKill command and shutdown continues normally - If fakeReboot was set, we spawn a background thread which issues 'system_reset' to perform a warm reboot of the guest hardware. Then it issues 'cont' to start the CPUs again * src/qemu/qemu_command.c: Add -no-shutdown flag if we have JSON support * src/qemu/qemu_domain.h: Add 'fakeReboot' flag to qemuDomainObjPrivate struct * src/qemu/qemu_driver.c: Fake reboot using the system_powerdown command if JSON support is available * src/qemu/qemu_monitor.c, src/qemu/qemu_monitor.h, src/qemu/qemu_monitor_json.c, src/qemu/qemu_monitor_json.h, src/qemu/qemu_monitor_text.c, src/qemu/qemu_monitor_text.h: Add binding for system_reset command * src/qemu/qemu_process.c: Reset the guest & start CPUs if fakeReboot is set
This commit is contained in:
parent
b17b4afafd
commit
42f43592be
@ -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");
|
||||
|
||||
|
@ -91,6 +91,8 @@ struct _qemuDomainObjPrivate {
|
||||
|
||||
virBitmapPtr qemuCaps;
|
||||
char *lockState;
|
||||
|
||||
bool fakeReboot;
|
||||
};
|
||||
|
||||
struct qemuDomainWatchdogEvent
|
||||
|
@ -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 */
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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,
|
||||
|
@ -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 } ]
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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)) {
|
||||
|
5
tests/qemuxml2argvdata/qemuxml2argv-monitor-json.args
Normal file
5
tests/qemuxml2argvdata/qemuxml2argv-monitor-json.args
Normal file
@ -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
|
24
tests/qemuxml2argvdata/qemuxml2argv-monitor-json.xml
Normal file
24
tests/qemuxml2argvdata/qemuxml2argv-monitor-json.xml
Normal file
@ -0,0 +1,24 @@
|
||||
<domain type='qemu'>
|
||||
<name>encryptdisk</name>
|
||||
<uuid>496898a6-e6ff-f7c8-5dc2-3cf410945ee9</uuid>
|
||||
<memory>219100</memory>
|
||||
<currentMemory>219100</currentMemory>
|
||||
<vcpu>1</vcpu>
|
||||
<os>
|
||||
<type arch='i686' machine='pc'>hvm</type>
|
||||
<boot dev='hd'/>
|
||||
</os>
|
||||
<clock offset='utc'/>
|
||||
<on_poweroff>destroy</on_poweroff>
|
||||
<on_reboot>restart</on_reboot>
|
||||
<on_crash>destroy</on_crash>
|
||||
<devices>
|
||||
<emulator>/usr/bin/qemu</emulator>
|
||||
<disk type='file' device='disk'>
|
||||
<driver name='qemu' type='qcow2'/>
|
||||
<source file='/dev/hda1'/>
|
||||
<target dev='hda'/>
|
||||
</disk>
|
||||
<memballoon model='virtio'/>
|
||||
</devices>
|
||||
</domain>
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user