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:
Daniel P. Berrange 2011-06-15 17:49:58 +01:00
parent b17b4afafd
commit 42f43592be
13 changed files with 267 additions and 8 deletions

View File

@ -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");

View File

@ -91,6 +91,8 @@ struct _qemuDomainObjPrivate {
virBitmapPtr qemuCaps;
char *lockState;
bool fakeReboot;
};
struct qemuDomainWatchdogEvent

View File

@ -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 */

View File

@ -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)
{

View File

@ -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,

View File

@ -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 } ]

View File

@ -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);

View File

@ -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)
{

View File

@ -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);

View File

@ -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)) {

View 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

View 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>

View File

@ -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);