]> git.ipfire.org Git - thirdparty/libvirt.git/commitdiff
Support reboots with the QEMU driver
authorDaniel P. Berrange <berrange@redhat.com>
Wed, 15 Jun 2011 16:49:58 +0000 (17:49 +0100)
committerDaniel P. Berrange <berrange@redhat.com>
Fri, 24 Jun 2011 11:13:47 +0000 (12:13 +0100)
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

13 files changed:
src/qemu/qemu_command.c
src/qemu/qemu_domain.h
src/qemu/qemu_driver.c
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
src/qemu/qemu_process.c
tests/qemuxml2argvdata/qemuxml2argv-monitor-json.args [new file with mode: 0644]
tests/qemuxml2argvdata/qemuxml2argv-monitor-json.xml [new file with mode: 0644]
tests/qemuxml2argvtest.c

index b517e1a6e204e8bcb793887b365eca806d58e900..7ac1faf9f5d2f2a22d9f125f4024b9373e27a534 100644 (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");
 
index 3d041fc074850ad7240e5fbf5a58034bda87fb0f..f282df2780322910d1f5a562f695b6d453db785c 100644 (file)
@@ -91,6 +91,8 @@ struct _qemuDomainObjPrivate {
 
     virBitmapPtr qemuCaps;
     char *lockState;
+
+    bool fakeReboot;
 };
 
 struct qemuDomainWatchdogEvent
index 2f416c8949ff431d91ee3c488d34e2af7a59740f..18fa4e0cd81937d157327659123e420ae782c54f 100644 (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 */
index 89a3f642a269df42fbb7503e15b547302bc2c3fd..2aa5c9a97763433b4ae93a40cab7dea02b147afd 100644 (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)
 {
index 3bb026900eb83c51a0ceabbd6decedb4b03d1aed..ae74a3ceb663b9eb747c4ce839d286915bd64f0b 100644 (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,
index 56ec65bb18561c308666c2231976b73fd4dc82ad..6cc6ea786106920b8fd9ca92a26ebf43707b5602 100644 (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 } ]
index 393d8fcaa98042a07409296c4d62fe6fa00033fd..a72bf7c6b4efd408633de0e74880a19da3893acb 100644 (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);
index a16ea91e1eef80a285cf645ce4b9a8112841c6ef..67333aaee131b3da570b17f24e3118bf75dbe8eb 100644 (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)
 {
index 4fa506462096834244dc1da7c52ca1e37cae48fe..75365574232c09d26dd4ec5c885fde44c040696e 100644 (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);
index b4c732db4c895ff90058d1b1108fe5cc51efecad..f8f95e2db09af267a503f1722cfea1551b209543 100644 (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)) {
diff --git a/tests/qemuxml2argvdata/qemuxml2argv-monitor-json.args b/tests/qemuxml2argvdata/qemuxml2argv-monitor-json.args
new file mode 100644 (file)
index 0000000..8d8e43e
--- /dev/null
@@ -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 (file)
index 0000000..1901715
--- /dev/null
@@ -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>
index 782664a4e3e97fde33664fd37d3d06704702204b..ec1f4b58336f13f4011248d6ce1db1ab1ad2fbed 100644 (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);