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) def->onReboot != VIR_DOMAIN_LIFECYCLE_RESTART)
virCommandAddArg(cmd, "-no-reboot"); 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))) if (!(def->features & (1 << VIR_DOMAIN_FEATURE_ACPI)))
virCommandAddArg(cmd, "-no-acpi"); virCommandAddArg(cmd, "-no-acpi");

View File

@ -91,6 +91,8 @@ struct _qemuDomainObjPrivate {
virBitmapPtr qemuCaps; virBitmapPtr qemuCaps;
char *lockState; char *lockState;
bool fakeReboot;
}; };
struct qemuDomainWatchdogEvent 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; struct qemud_driver *driver = dom->conn->privateData;
virDomainObjPtr vm; virDomainObjPtr vm;
int ret = -1; int ret = -1;
@ -1460,6 +1460,8 @@ static int qemudDomainShutdown(virDomainPtr dom) {
ret = qemuMonitorSystemPowerdown(priv->mon); ret = qemuMonitorSystemPowerdown(priv->mon);
qemuDomainObjExitMonitor(vm); qemuDomainObjExitMonitor(vm);
priv->fakeReboot = false;
endjob: endjob:
if (qemuDomainObjEndJob(vm) == 0) if (qemuDomainObjEndJob(vm) == 0)
vm = NULL; 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) { static int qemudDomainDestroy(virDomainPtr dom) {
struct qemud_driver *driver = dom->conn->privateData; struct qemud_driver *driver = dom->conn->privateData;
virDomainObjPtr vm; virDomainObjPtr vm;
int ret = -1; int ret = -1;
virDomainEventPtr event = NULL; virDomainEventPtr event = NULL;
qemuDomainObjPrivatePtr priv;
qemuDriverLock(driver); qemuDriverLock(driver);
vm = virDomainFindByUUID(&driver->domains, dom->uuid); vm = virDomainFindByUUID(&driver->domains, dom->uuid);
@ -1487,6 +1546,9 @@ static int qemudDomainDestroy(virDomainPtr dom) {
goto cleanup; goto cleanup;
} }
priv = vm->privateData;
priv->fakeReboot = false;
/* Although qemuProcessStop does this already, there may /* Although qemuProcessStop does this already, there may
* be an outstanding job active. We want to make sure we * be an outstanding job active. We want to make sure we
* can kill the process even if a job is active. Killing * can kill the process even if a job is active. Killing
@ -8336,7 +8398,8 @@ static virDriver qemuDriver = {
.domainLookupByName = qemudDomainLookupByName, /* 0.2.0 */ .domainLookupByName = qemudDomainLookupByName, /* 0.2.0 */
.domainSuspend = qemudDomainSuspend, /* 0.2.0 */ .domainSuspend = qemudDomainSuspend, /* 0.2.0 */
.domainResume = qemudDomainResume, /* 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 */ .domainDestroy = qemudDomainDestroy, /* 0.2.0 */
.domainGetOSType = qemudDomainGetOSType, /* 0.2.2 */ .domainGetOSType = qemudDomainGetOSType, /* 0.2.2 */
.domainGetMaxMemory = qemudDomainGetMaxMemory, /* 0.4.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 qemuMonitorGetCPUInfo(qemuMonitorPtr mon,
int **pids) int **pids)
{ {

View File

@ -194,6 +194,7 @@ int qemuMonitorStartCPUs(qemuMonitorPtr mon,
int qemuMonitorStopCPUs(qemuMonitorPtr mon); int qemuMonitorStopCPUs(qemuMonitorPtr mon);
int qemuMonitorGetStatus(qemuMonitorPtr mon, bool *running); int qemuMonitorGetStatus(qemuMonitorPtr mon, bool *running);
int qemuMonitorSystemReset(qemuMonitorPtr mon);
int qemuMonitorSystemPowerdown(qemuMonitorPtr mon); int qemuMonitorSystemPowerdown(qemuMonitorPtr mon);
int qemuMonitorGetCPUInfo(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": 0, "current": true, "halted": false, "pc": 3227107138 },
* { "CPU": 1, "current": false, "halted": true, "pc": 7108165 } ] * { "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 qemuMonitorJSONGetStatus(qemuMonitorPtr mon, bool *running);
int qemuMonitorJSONSystemPowerdown(qemuMonitorPtr mon); int qemuMonitorJSONSystemPowerdown(qemuMonitorPtr mon);
int qemuMonitorJSONSystemReset(qemuMonitorPtr mon);
int qemuMonitorJSONGetCPUInfo(qemuMonitorPtr mon, int qemuMonitorJSONGetCPUInfo(qemuMonitorPtr mon,
int **pids); 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 qemuMonitorTextGetCPUInfo(qemuMonitorPtr mon,
int **pids) int **pids)
{ {

View File

@ -46,6 +46,7 @@ int qemuMonitorTextStopCPUs(qemuMonitorPtr mon);
int qemuMonitorTextGetStatus(qemuMonitorPtr mon, bool *running); int qemuMonitorTextGetStatus(qemuMonitorPtr mon, bool *running);
int qemuMonitorTextSystemPowerdown(qemuMonitorPtr mon); int qemuMonitorTextSystemPowerdown(qemuMonitorPtr mon);
int qemuMonitorTextSystemReset(qemuMonitorPtr mon);
int qemuMonitorTextGetCPUInfo(qemuMonitorPtr mon, int qemuMonitorTextGetCPUInfo(qemuMonitorPtr mon,
int **pids); 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 static int
qemuProcessHandleShutdown(qemuMonitorPtr mon ATTRIBUTE_UNUSED, qemuProcessHandleShutdown(qemuMonitorPtr mon ATTRIBUTE_UNUSED,
virDomainObjPtr vm) virDomainObjPtr vm)
{ {
qemuDomainObjPrivatePtr priv = vm->privateData;
VIR_DEBUG("vm=%p", vm);
virDomainObjLock(vm); virDomainObjLock(vm);
((qemuDomainObjPrivatePtr) vm->privateData)->gotShutdown = true; priv->gotShutdown = true;
virDomainObjUnlock(vm); 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; 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 int
qemuProcessStartCPUs(struct qemud_driver *driver, virDomainObjPtr vm, qemuProcessStartCPUs(struct qemud_driver *driver, virDomainObjPtr vm,
virConnectPtr conn, virDomainRunningReason reason) virConnectPtr conn, virDomainRunningReason reason)
@ -2349,6 +2444,7 @@ int qemuProcessStart(virConnectPtr conn,
goto cleanup; goto cleanup;
vm->def->id = driver->nextvmid++; vm->def->id = driver->nextvmid++;
priv->fakeReboot = false;
/* Run an early hook to set-up missing devices */ /* Run an early hook to set-up missing devices */
if (virHookPresent(VIR_HOOK_DRIVER_QEMU)) { 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, virBitmapPtr extraFlags,
const char *migrateFrom, const char *migrateFrom,
int migrateFd, int migrateFd,
bool json,
bool expectError) bool expectError)
{ {
char *expectargv = NULL; char *expectargv = NULL;
@ -116,7 +117,7 @@ static int testCompareXMLToArgvFiles(const char *xml,
} }
if (!(cmd = qemuBuildCommandLine(conn, &driver, if (!(cmd = qemuBuildCommandLine(conn, &driver,
vmdef, &monitor_chr, false, extraFlags, vmdef, &monitor_chr, json, extraFlags,
migrateFrom, migrateFd, NULL, migrateFrom, migrateFd, NULL,
VIR_VM_OP_NO_OP))) VIR_VM_OP_NO_OP)))
goto fail; goto fail;
@ -168,6 +169,7 @@ struct testInfo {
virBitmapPtr extraFlags; virBitmapPtr extraFlags;
const char *migrateFrom; const char *migrateFrom;
int migrateFd; int migrateFd;
bool json;
bool expectError; bool expectError;
}; };
@ -186,8 +188,8 @@ testCompareXMLToArgvHelper(const void *data)
goto cleanup; goto cleanup;
result = testCompareXMLToArgvFiles(xml, args, info->extraFlags, result = testCompareXMLToArgvFiles(xml, args, info->extraFlags,
info->migrateFrom, info->migrateFd, info->migrateFrom, info->migrateFd,
info->expectError); info->json, info->expectError);
cleanup: cleanup:
free(xml); free(xml);
@ -202,6 +204,7 @@ mymain(void)
{ {
int ret = 0; int ret = 0;
char *map = NULL; char *map = NULL;
bool json = false;
abs_top_srcdir = getenv("abs_top_srcdir"); abs_top_srcdir = getenv("abs_top_srcdir");
if (!abs_top_srcdir) if (!abs_top_srcdir)
@ -229,7 +232,7 @@ mymain(void)
# define DO_TEST_FULL(name, migrateFrom, migrateFd, expectError, ...) \ # define DO_TEST_FULL(name, migrateFrom, migrateFd, expectError, ...) \
do { \ do { \
struct testInfo info = { \ struct testInfo info = { \
name, NULL, migrateFrom, migrateFd, expectError \ name, NULL, migrateFrom, migrateFd, json, expectError \
}; \ }; \
if (!(info.extraFlags = qemuCapsNew())) \ if (!(info.extraFlags = qemuCapsNew())) \
return EXIT_FAILURE; \ return EXIT_FAILURE; \
@ -529,6 +532,11 @@ mymain(void)
QEMU_CAPS_DRIVE, QEMU_CAPS_DEVICE, QEMU_CAPS_NODEFCONFIG, QEMU_CAPS_DRIVE, QEMU_CAPS_DEVICE, QEMU_CAPS_NODEFCONFIG,
QEMU_CAPS_PCI_MULTIFUNCTION); 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); free(driver.stateDir);
virCapabilitiesFree(driver.caps); virCapabilitiesFree(driver.caps);
free(map); free(map);