[libvirt] [PATCH 8/8] Implement code to attach to external QEMU instances.

Daniel P. Berrange berrange at redhat.com
Mon Jun 20 13:52:01 UTC 2011


Given a PID, the QEMU driver reads /proc/$PID/cmdline and
/proc/$PID/environ to get the configuration. This is fed
into the ARGV->XML convertor to build an XML configuration
for the process.

/proc/$PID/exe is resolved to identify the full command
binary path

After checking for name/uuid uniqueness, an attempt is
made to connect to the monitor socket. If successful
then 'info status' and 'info kvm' are issued to determine
whether the CPUs are running and if KVM is enabled.

* src/qemu/qemu_driver.c: Implement virDomainQemuAttach
* src/qemu/qemu_process.h, src/qemu/qemu_process.c: Add
  qemuProcessAttach to connect to the monitor of an
  existing QEMU process
---
 src/conf/domain_conf.c  |    3 +-
 src/conf/domain_conf.h  |    1 +
 src/qemu/qemu_command.c |    2 +
 src/qemu/qemu_driver.c  |   91 +++++++++++++++++++-
 src/qemu/qemu_process.c |  220 ++++++++++++++++++++++++++++++++++++++++++++---
 src/qemu/qemu_process.h |    8 ++
 6 files changed, 308 insertions(+), 17 deletions(-)

diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c
index 5360863..8de6ce6 100644
--- a/src/conf/domain_conf.c
+++ b/src/conf/domain_conf.c
@@ -55,7 +55,8 @@ VIR_ENUM_IMPL(virDomainTaint, VIR_DOMAIN_TAINT_LAST,
               "custom-monitor",
               "high-privileges",
               "shell-scripts",
-              "disk-probing");
+              "disk-probing",
+              "external-launch");
 
 VIR_ENUM_IMPL(virDomainVirt, VIR_DOMAIN_VIRT_LAST,
               "qemu",
diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h
index ff5c28d..90a3229 100644
--- a/src/conf/domain_conf.h
+++ b/src/conf/domain_conf.h
@@ -1248,6 +1248,7 @@ enum virDomainTaintFlags {
     VIR_DOMAIN_TAINT_HIGH_PRIVILEGES,  /* Running with undesirably high privileges */
     VIR_DOMAIN_TAINT_SHELL_SCRIPTS,    /* Network configuration using opaque shell scripts */
     VIR_DOMAIN_TAINT_DISK_PROBING,     /* Relying on potentially unsafe disk format probing */
+    VIR_DOMAIN_TAINT_EXTERNAL_LAUNCH,  /* Externally launched guest domain */
 
     VIR_DOMAIN_TAINT_LAST
 };
diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c
index 41093c0..f5cbf6b 100644
--- a/src/qemu/qemu_command.c
+++ b/src/qemu/qemu_command.c
@@ -6156,6 +6156,8 @@ virDomainDefPtr qemuParseCommandLine(virCapsPtr caps,
                 if (!(def->name = strndup(val, process - val)))
                     goto no_memory;
             }
+            if (STREQ(def->name, ""))
+                VIR_FREE(def->name);
         } else if (STREQ(arg, "-M")) {
             WANT_VALUE();
             if (!(def->os.machine = strdup(val)))
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
index 20a3d59..282b69b 100644
--- a/src/qemu/qemu_driver.c
+++ b/src/qemu/qemu_driver.c
@@ -3924,7 +3924,7 @@ qemudCanonicalizeMachineFromInfo(virDomainDefPtr def,
         if (!machine->canonical)
             continue;
 
-        if (STRNEQ(def->os.machine, machine->name))
+        if (def->os.machine && STRNEQ(def->os.machine, machine->name))
             continue;
 
         if (!(*canonical = strdup(machine->canonical))) {
@@ -3951,7 +3951,7 @@ qemudCanonicalizeMachineDirect(virDomainDefPtr def, char **canonical)
         if (!machines[i]->canonical)
             continue;
 
-        if (STRNEQ(def->os.machine, machines[i]->name))
+        if (def->os.machine && STRNEQ(def->os.machine, machines[i]->name))
             continue;
 
         *canonical = machines[i]->canonical;
@@ -8138,6 +8138,92 @@ cleanup:
 }
 
 
+static virDomainPtr qemuDomainAttach(virConnectPtr conn,
+                                     unsigned long long pid,
+                                     unsigned int flags)
+{
+    struct qemud_driver *driver = conn->privateData;
+    virDomainObjPtr vm = NULL;
+    virDomainDefPtr def = NULL;
+    virDomainPtr dom = NULL;
+    virDomainChrSourceDefPtr monConfig = NULL;
+    bool monJSON = false;
+    char *pidfile;
+
+    virCheckFlags(0, NULL);
+
+    qemuDriverLock(driver);
+
+    if (!(def = qemuParseCommandLinePid(driver->caps, pid,
+                                        &pidfile, &monConfig, &monJSON)))
+        goto cleanup;
+
+    if (!monConfig) {
+        qemuReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+                        _("No monitor connection for pid %llu"),
+                        pid);
+        goto cleanup;
+    }
+    if (monConfig->type != VIR_DOMAIN_CHR_TYPE_UNIX) {
+        qemuReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+                        _("Cannot connect to monitor connection of type '%s' for pid %llu"),
+                        virDomainChrTypeToString(monConfig->type), pid);
+        goto cleanup;
+    }
+
+    if (!(def->name) &&
+        virAsprintf(&def->name, "attach-pid-%llu", pid) < 0) {
+        virReportOOMError();
+        goto cleanup;
+    }
+
+    if (virDomainObjIsDuplicate(&driver->domains, def, 1) < 0)
+        goto cleanup;
+
+    if (qemudCanonicalizeMachine(driver, def) < 0)
+        goto cleanup;
+
+    if (qemuDomainAssignPCIAddresses(def) < 0)
+        goto cleanup;
+
+    if (!(vm = virDomainAssignDef(driver->caps,
+                                  &driver->domains,
+                                  def, false)))
+        goto cleanup;
+
+    def = NULL;
+
+    if (qemuDomainObjBeginJobWithDriver(driver, vm) < 0)
+        goto cleanup;
+
+    if (qemuProcessAttach(conn, driver, vm, pid,
+                          pidfile, monConfig, monJSON) < 0) {
+        monConfig = NULL;
+        goto endjob;
+    }
+
+    monConfig = NULL;
+
+    dom = virGetDomain(conn, vm->def->name, vm->def->uuid);
+    if (dom) dom->id = vm->def->id;
+
+endjob:
+    if (qemuDomainObjEndJob(vm) == 0) {
+        vm = NULL;
+        goto cleanup;
+    }
+
+cleanup:
+    virDomainDefFree(def);
+    virDomainChrSourceDefFree(monConfig);
+    if (vm)
+        virDomainObjUnlock(vm);
+    qemuDriverUnlock(driver);
+    VIR_FREE(pidfile);
+    return dom;
+}
+
+
 static int
 qemuDomainOpenConsole(virDomainPtr dom,
                       const char *devname,
@@ -8436,6 +8522,7 @@ static virDriver qemuDriver = {
     .domainRevertToSnapshot = qemuDomainRevertToSnapshot, /* 0.8.0 */
     .domainSnapshotDelete = qemuDomainSnapshotDelete, /* 0.8.0 */
     .qemuDomainMonitorCommand = qemuDomainMonitorCommand, /* 0.8.3 */
+    .qemuDomainAttach = qemuDomainAttach, /* 0.9.3 */
     .domainOpenConsole = qemuDomainOpenConsole, /* 0.8.6 */
     .domainInjectNMI = qemuDomainInjectNMI, /* 0.9.2 */
     .domainMigrateBegin3 = qemuDomainMigrateBegin3, /* 0.9.2 */
diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c
index 16d0c0a..5711a04 100644
--- a/src/qemu/qemu_process.c
+++ b/src/qemu/qemu_process.c
@@ -59,6 +59,7 @@
 #define VIR_FROM_THIS VIR_FROM_QEMU
 
 #define START_POSTFIX ": starting up\n"
+#define ATTACH_POSTFIX ": attaching\n"
 #define SHUTDOWN_POSTFIX ": shutting down\n"
 
 /**
@@ -1063,31 +1064,34 @@ qemuProcessReadLogFD(int logfd, char *buf, int maxlen, int off)
     tmpbuf[ret] = '\0';
 }
 
+
 static int
 qemuProcessWaitForMonitor(struct qemud_driver* driver,
                           virDomainObjPtr vm,
                           virBitmapPtr qemuCaps,
                           off_t pos)
 {
-    char *buf;
+    char *buf = NULL;
     size_t buf_size = 4096; /* Plenty of space to get startup greeting */
-    int logfd;
+    int logfd = -1;
     int ret = -1;
     virHashTablePtr paths = NULL;
     qemuDomainObjPrivatePtr priv;
 
-    if ((logfd = qemuDomainOpenLog(driver, vm, pos)) < 0)
-        return -1;
+    if (pos != -1) {
+        if ((logfd = qemuDomainOpenLog(driver, vm, pos)) < 0)
+            return -1;
 
-    if (VIR_ALLOC_N(buf, buf_size) < 0) {
-        virReportOOMError();
-        return -1;
-    }
+        if (VIR_ALLOC_N(buf, buf_size) < 0) {
+            virReportOOMError();
+            return -1;
+        }
 
-    if (qemuProcessReadLogOutput(vm, logfd, buf, buf_size,
-                                 qemuProcessFindCharDevicePTYs,
-                                 "console", 30) < 0)
-        goto closelog;
+        if (qemuProcessReadLogOutput(vm, logfd, buf, buf_size,
+                                     qemuProcessFindCharDevicePTYs,
+                                     "console", 30) < 0)
+            goto closelog;
+    }
 
     VIR_DEBUG("Connect monitor to %p '%s'", vm, vm->def->name);
     if (qemuConnectMonitor(driver, vm) < 0) {
@@ -1124,8 +1128,6 @@ cleanup:
         ret = -1;
     }
 
-    VIR_FREE(buf);
-
 closelog:
     if (VIR_CLOSE(logfd) < 0) {
         char ebuf[1024];
@@ -1133,6 +1135,8 @@ closelog:
                  virStrerror(errno, ebuf, sizeof ebuf));
     }
 
+    VIR_FREE(buf);
+
     return ret;
 }
 
@@ -2943,3 +2947,191 @@ retry:
         virFreeError(orig_err);
     }
 }
+
+
+int qemuProcessAttach(virConnectPtr conn ATTRIBUTE_UNUSED,
+                      struct qemud_driver *driver,
+                      virDomainObjPtr vm,
+                      int pid,
+                      const char *pidfile,
+                      virDomainChrSourceDefPtr monConfig,
+                      bool monJSON)
+{
+    char ebuf[1024];
+    int logfile = -1;
+    char *timestamp;
+    qemuDomainObjPrivatePtr priv = vm->privateData;
+    bool running = true;
+    virSecurityLabelPtr seclabel = NULL;
+
+    VIR_DEBUG("Beginning VM attach process");
+
+    if (virDomainObjIsActive(vm)) {
+        qemuReportError(VIR_ERR_OPERATION_INVALID,
+                        "%s", _("VM is already active"));
+        return -1;
+    }
+
+    /* Do this upfront, so any part of the startup process can add
+     * runtime state to vm->def that won't be persisted. This let's us
+     * report implicit runtime defaults in the XML, like vnc listen/socket
+     */
+    VIR_DEBUG("Setting current domain def as transient");
+    if (virDomainObjSetDefTransient(driver->caps, vm, true) < 0)
+        goto cleanup;
+
+    vm->def->id = driver->nextvmid++;
+
+    if (virFileMakePath(driver->logDir) != 0) {
+        virReportSystemError(errno,
+                             _("cannot create log directory %s"),
+                             driver->logDir);
+        goto cleanup;
+    }
+
+    VIR_FREE(priv->pidfile);
+    if (pidfile &&
+        !(priv->pidfile = strdup(pidfile)))
+        goto no_memory;
+
+    VIR_DEBUG("Detect security driver config");
+    vm->def->seclabel.type = VIR_DOMAIN_SECLABEL_STATIC;
+    if (VIR_ALLOC(seclabel) < 0)
+        goto no_memory;
+    if (virSecurityManagerGetProcessLabel(driver->securityManager,
+                                          vm, seclabel) < 0)
+        goto cleanup;
+    if (!(vm->def->seclabel.model = strdup(driver->caps->host.secModel.model)))
+        goto no_memory;
+    if (!(vm->def->seclabel.label = strdup(seclabel->label)))
+        goto no_memory;
+
+    VIR_DEBUG("Creating domain log file");
+    if ((logfile = qemuDomainCreateLog(driver, vm, false)) < 0)
+        goto cleanup;
+
+    VIR_DEBUG("Determining emulator version");
+    qemuCapsFree(priv->qemuCaps);
+    priv->qemuCaps = NULL;
+    if (qemuCapsExtractVersionInfo(vm->def->emulator,
+                                   vm->def->os.arch,
+                                   NULL,
+                                   &priv->qemuCaps) < 0)
+        goto cleanup;
+
+    if (VIR_ALLOC(priv->monConfig) < 0) {
+        virReportOOMError();
+        goto cleanup;
+    }
+
+    VIR_DEBUG("Preparing monitor state");
+    priv->monConfig = monConfig;
+    monConfig = NULL;
+    priv->monJSON = monJSON;
+
+    priv->gotShutdown = false;
+
+    /*
+     * Normally PCI addresses are assigned in the virDomainCreate
+     * or virDomainDefine methods. We might still need to assign
+     * some here to cope with the question of upgrades. Regardless
+     * we also need to populate the PCi address set cache for later
+     * use in hotplug
+     */
+    if (qemuCapsGet(priv->qemuCaps, QEMU_CAPS_DEVICE)) {
+        VIR_DEBUG("Assigning domain PCI addresses");
+        /* Populate cache with current addresses */
+        if (priv->pciaddrs) {
+            qemuDomainPCIAddressSetFree(priv->pciaddrs);
+            priv->pciaddrs = NULL;
+        }
+        if (!(priv->pciaddrs = qemuDomainPCIAddressSetCreate(vm->def)))
+            goto cleanup;
+
+        /* Assign any remaining addresses */
+        if (qemuAssignDevicePCISlots(vm->def, priv->pciaddrs) < 0)
+            goto cleanup;
+
+        priv->persistentAddrs = 1;
+    } else {
+        priv->persistentAddrs = 0;
+    }
+
+    if ((timestamp = virTimestamp()) == NULL) {
+        virReportOOMError();
+        goto cleanup;
+    } else {
+        if (safewrite(logfile, timestamp, strlen(timestamp)) < 0 ||
+            safewrite(logfile, ATTACH_POSTFIX, strlen(ATTACH_POSTFIX)) < 0) {
+            VIR_WARN("Unable to write timestamp to logfile: %s",
+                     virStrerror(errno, ebuf, sizeof ebuf));
+        }
+
+        VIR_FREE(timestamp);
+    }
+
+    qemuDomainObjTaint(driver, vm, VIR_DOMAIN_TAINT_EXTERNAL_LAUNCH, logfile);
+
+    vm->pid = pid;
+
+    VIR_DEBUG("Waiting for monitor to show up");
+    if (qemuProcessWaitForMonitor(driver, vm, priv->qemuCaps, -1) < 0)
+        goto cleanup;
+
+    VIR_DEBUG("Detecting VCPU PIDs");
+    if (qemuProcessDetectVcpuPIDs(driver, vm) < 0)
+        goto cleanup;
+
+    /* If we have -device, then addresses are assigned explicitly.
+     * If not, then we have to detect dynamic ones here */
+    if (!qemuCapsGet(priv->qemuCaps, QEMU_CAPS_DEVICE)) {
+        VIR_DEBUG("Determining domain device PCI addresses");
+        if (qemuProcessInitPCIAddresses(driver, vm) < 0)
+            goto cleanup;
+    }
+
+    VIR_DEBUG("Getting initial memory amount");
+    qemuDomainObjEnterMonitorWithDriver(driver, vm);
+    if (qemuMonitorGetBalloonInfo(priv->mon, &vm->def->mem.cur_balloon) < 0) {
+        qemuDomainObjExitMonitorWithDriver(driver, vm);
+        goto cleanup;
+    }
+    if (qemuMonitorGetStatus(priv->mon, &running) < 0) {
+        qemuDomainObjExitMonitorWithDriver(driver, vm);
+        goto cleanup;
+    }
+    if (qemuMonitorGetVirtType(priv->mon, &vm->def->virtType) < 0) {
+        qemuDomainObjExitMonitorWithDriver(driver, vm);
+        goto cleanup;
+    }
+    qemuDomainObjExitMonitorWithDriver(driver, vm);
+
+    if (!virDomainObjIsActive(vm))
+        goto cleanup;
+
+    if (running)
+        virDomainObjSetState(vm, VIR_DOMAIN_RUNNING,
+                             VIR_DOMAIN_RUNNING_UNPAUSED);
+    else
+        virDomainObjSetState(vm, VIR_DOMAIN_PAUSED, VIR_DOMAIN_PAUSED_UNKNOWN);
+
+    VIR_DEBUG("Writing domain status to disk");
+    if (virDomainSaveStatus(driver->caps, driver->stateDir, vm) < 0)
+        goto cleanup;
+
+    VIR_FORCE_CLOSE(logfile);
+    VIR_FREE(seclabel);
+
+    return 0;
+
+no_memory:
+    virReportOOMError();
+cleanup:
+    /* We jump here if we failed to start the VM for any reason, or
+     * if we failed to initialize the now running VM. kill it off and
+     * pretend we never started it */
+    VIR_FORCE_CLOSE(logfile);
+    VIR_FREE(seclabel);
+    virDomainChrSourceDefFree(monConfig);
+    return -1;
+}
diff --git a/src/qemu/qemu_process.h b/src/qemu/qemu_process.h
index 7ec9d7d..e2815fb 100644
--- a/src/qemu/qemu_process.h
+++ b/src/qemu/qemu_process.h
@@ -57,4 +57,12 @@ void qemuProcessStop(struct qemud_driver *driver,
 
 void qemuProcessKill(virDomainObjPtr vm);
 
+int qemuProcessAttach(virConnectPtr conn,
+                      struct qemud_driver *driver,
+                      virDomainObjPtr vm,
+                      int pid,
+                      const char *pidfile,
+                      virDomainChrSourceDefPtr monConfig,
+                      bool monJSON);
+
 #endif /* __QEMU_PROCESS_H__ */
-- 
1.7.4.4




More information about the libvir-list mailing list