[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]

[virt-tools-list] [PATCH DISCUSSION ONLY] Add inspection thread to virt-manager.



[This patch is incomplete and just for discussion]

Using libguestfs inspection[1], we can find out a lot about a virtual
machine, such as what operating system is installed, the OS version,
the OS distro, the hostname, the (real) disk usage, what apps are
installed, and lots more. It would be nice if virt-manager could use
this information: for example we could change the generic "computer"
icon into a Windows or Fedora logo if Windows or Fedora was installed
in the virtual machine.

This patch adds a daemon thread running in the background which
performs batch inspection on the virt-manager VMs.  If inspection is
successful, it then passes the results up to the UI (ie. the
vmmManager object) to be displayed.

Currently I've just added the inspected hostname to the UI as you can
see in this screenshot:

  http://oirase.annexia.org/tmp/vmm-hostnames.png

In future there is room for much greater customization of the UI, such
as changing the icons, displaying disk usage and more.

The background thread is transparent to the UI.  Well, in fact because
of a bug in libguestfs, it is currently NOT transparent because we
weren't releasing the Python GIL across libguestfs calls.  If you
apply the following patch to libguestfs, then it *is* transparent and
doesn't interrupt the UI:

  https://www.redhat.com/archives/libguestfs/2011-April/msg00076.html

I think that virt-manager should probably cache the inspection data
across runs.  However I don't know if virt-manager has a mechanism to
do this already or if we'd need to add one.

Also this patch doesn't yet change ./configure, so it'll just fail if
the python-libguestfs package is not installed.

Rich.

[1] http://libguestfs.org/virt-inspector.1.html

-- 
Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones
Read my programming blog: http://rwmj.wordpress.com
Fedora now supports 80 OCaml packages (the OPEN alternative to F#)
http://cocan.org/getting_started_with_ocaml_on_red_hat_and_fedora
>From 11278a7509e4edbcb28eac4c2c4a50fc8d68342e Mon Sep 17 00:00:00 2001
From: Richard W.M. Jones <rjones redhat com>
Date: Mon, 18 Apr 2011 17:44:51 +0100
Subject: [PATCH] Add inspection thread to virt-manager.

---
 src/virtManager/engine.py     |   14 ++++
 src/virtManager/inspection.py |  162 +++++++++++++++++++++++++++++++++++++++++
 src/virtManager/manager.py    |   26 ++++++-
 3 files changed, 200 insertions(+), 2 deletions(-)
 create mode 100644 src/virtManager/inspection.py

diff --git a/src/virtManager/engine.py b/src/virtManager/engine.py
index 383deb3..40d5b39 100644
--- a/src/virtManager/engine.py
+++ b/src/virtManager/engine.py
@@ -45,6 +45,7 @@ from virtManager.create import vmmCreate
 from virtManager.host import vmmHost
 from virtManager.error import vmmErrorDialog
 from virtManager.systray import vmmSystray
+from virtManager.inspection import vmmInspection
 import virtManager.uihelpers as uihelpers
 import virtManager.util as util
 
@@ -239,6 +240,9 @@ class vmmEngine(vmmGObject):
         if not self.config.support_threading:
             logging.debug("Libvirt doesn't support threading, skipping.")
 
+        self.inspection_thread = None
+        self.create_inspection_thread()
+
         # Counter keeping track of how many manager and details windows
         # are open. When it is decremented to 0, close the app or
         # keep running in system tray if enabled
@@ -533,6 +537,16 @@ class vmmEngine(vmmGObject):
         logging.debug("Exiting app normally.")
         gtk.main_quit()
 
+    def create_inspection_thread(self):
+        if not self.config.support_threading:
+            logging.debug("No inspection thread because "
+                          "libvirt is not thread-safe.")
+            return
+        self.inspection_thread = vmmInspection(self)
+        self.inspection_thread.daemon = True
+        self.inspection_thread.start()
+        return
+
     def add_connection(self, uri, readOnly=None, autoconnect=False):
         conn = self._check_connection(uri)
         if conn:
diff --git a/src/virtManager/inspection.py b/src/virtManager/inspection.py
new file mode 100644
index 0000000..70f2f52
--- /dev/null
+++ b/src/virtManager/inspection.py
@@ -0,0 +1,162 @@
+#
+# Copyright (C) 2011 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA 02110-1301 USA.
+#
+
+import sys
+import time
+import traceback
+from Queue import Queue
+from threading import Thread
+
+from guestfs import GuestFS
+
+import logging
+import util
+
+class vmmInspection(Thread):
+    _name = "inspection thread"
+    _wait = 60 # seconds
+
+    def __init__(self, engine):
+        Thread.__init__(self, name=self._name)
+        self._q = Queue()
+        self._engine = engine
+        self._vmcache = dict()
+
+    # Called by the main thread whenever a VM is added to vmlist.
+    def vm_added(self, connection, vmuuid):
+        obj = (connection, vmuuid)
+        self._q.put(obj)
+
+    def run(self):
+        # Wait a few seconds before we do anything.  This prevents
+        # inspection from being a burden for initial virt-manager
+        # interactivity (although it shouldn't affect interactivity at
+        # all).
+        logging.debug("%s: waiting" % self._name)
+        time.sleep(self._wait)
+
+        logging.debug("%s: ready" % self._name)
+
+        while True:
+            obj = self._q.get(True)
+            (connection, vmuuid) = obj
+
+            logging.debug("%s: %s: processing started, connection = %s" %
+                          (self._name, vmuuid, connection))
+            try:
+                self._process(connection, vmuuid)
+            except:
+                logging.debug("%s: %s: processing raised:\n%s" %
+                              (self._name, vmuuid, traceback.format_exc()))
+
+            self._q.task_done()
+            logging.debug("%s: %s: processing done" % (self._name, vmuuid))
+
+    def _process(self, connection, vmuuid):
+        if vmuuid in self._vmcache:
+            self._update_ui(connection, vmuuid)
+            return
+
+        if not connection or connection.is_remote():
+            logging.debug("%s: %s: no connection object or "
+                          "connection is remote" % (self._name, vmuuid))
+            return
+        vm = connection.get_vm(vmuuid)
+        if not vm:
+            logging.debug("%s: %s: no vm object" % (self._name, vmuuid))
+            return
+
+        xml = vm.get_xml()
+        if not xml:
+            logging.debug("%s: %s: cannot get domain XML" %
+                          (self._name, vmuuid))
+            return
+
+        g = GuestFS()
+        # One day we will be able to do ...
+        #g.add_libvirt_dom(...)
+        # but until that day comes ...
+        disks = self._get_disks_from_xml(xml)
+        for (disk, format) in disks:
+            g.add_drive_opts(disk, readonly=1, format=format)
+
+        g.launch()
+
+        # Inspect the operating system.
+        roots = g.inspect_os()
+        if len(roots) == 0:
+            logging.debug("%s: %s: no operating systems found" %
+                          (self._name, vmuuid))
+            return
+
+        # Arbitrarily pick the first root device.
+        root = roots[0]
+
+        # Inspection.
+        name = g.inspect_get_type (root)
+        distro = g.inspect_get_distro (root)
+        hostname = g.inspect_get_hostname (root)
+
+        self._vmcache[vmuuid] = (name, distro, hostname)
+
+        # Force the libguestfs handle to close right now.
+        del g
+
+        # Update the UI.
+        self._update_ui(connection, vmuuid)
+
+    # Call back into the manager (if one is displayed) to update the
+    # inspection data for the particular VM.
+    def _update_ui(self, connection, vmuuid):
+        wm = self._engine.windowManager
+        if not wm: return
+
+        name, distro, hostname = self._vmcache[vmuuid]
+
+        wm.vm_set_inspection_data(connection, vmuuid, name, distro, hostname)
+
+    # Get the list of disks belonging to this VM from the libvirt XML.
+    def _get_disks_from_xml(self, xml):
+        def f(doc, ctx):
+            nodes = ctx.xpathEval("//devices/disk")
+            disks = []
+            for node in nodes:
+                ctx.setContextNode(node)
+                types = ctx.xpathEval("./@type")
+
+                if types and types[0].content:
+                    t = types[0].content
+                    disk = None
+
+                    if t == "file":
+                        disk = ctx.xpathEval("./source/@file")
+                    elif t == "block":
+                        disk = ctx.xpathEval("./source/@dev")
+
+                    if disk and disk[0].content:
+                        d = disk[0].content
+                        types = ctx.xpathEval("./driver/@type")
+                        if types and types[0].content:
+                            disks.append((d, types[0].content))
+                        else:
+                            disks.append((d, None))
+
+            return disks
+
+        return util.xml_parse_wrapper(xml, f)
diff --git a/src/virtManager/manager.py b/src/virtManager/manager.py
index 7d90252..6fc8540 100644
--- a/src/virtManager/manager.py
+++ b/src/virtManager/manager.py
@@ -45,6 +45,7 @@ ROW_IS_CONN_CONNECTED = 8
 ROW_IS_VM = 9
 ROW_IS_VM_RUNNING = 10
 ROW_COLOR = 11
+ROW_HOSTNAME = 12
 
 # Columns in the tree view
 COL_NAME = 0
@@ -403,9 +404,9 @@ class vmmManager(vmmGObjectUI):
         self.window.get_widget("vm-notebook").set_show_tabs(False)
 
         # Handle, name, markup, status, status icon, key/uuid, hint, is conn,
-        # is conn connected, is vm, is vm running, fg color
+        # is conn connected, is vm, is vm running, fg color, hostname
         model = gtk.TreeStore(object, str, str, str, gtk.gdk.Pixbuf, str, str,
-                              bool, bool, bool, bool, gtk.gdk.Color)
+                              bool, bool, bool, bool, gtk.gdk.Color, str)
         vmlist.set_model(model)
         util.tooltip_wrapper(vmlist, ROW_HINT, "set_tooltip_column")
 
@@ -743,6 +744,9 @@ class vmmManager(vmmGObjectUI):
 
         self._append_vm(model, vm, connection)
 
+        if self.engine.inspection_thread:
+            self.engine.inspection_thread.vm_added(connection, vmuuid)
+
     def vm_removed(self, connection, uri_ignore, vmuuid):
         vmlist = self.window.get_widget("vm-list")
         model = vmlist.get_model()
@@ -755,6 +759,20 @@ class vmmManager(vmmGObjectUI):
                 del self.rows[self.vm_row_key(vm)]
                 break
 
+    def vm_set_inspection_data(self, connection, vmuuid,
+                               name, distro, hostname):
+        vm = connection.get_vm(vmuuid)
+        vmlist = self.window.get_widget("vm-list")
+        model = vmlist.get_model()
+
+        row_key = self.vm_row_key(vm)
+        if row_key not in self.rows:
+            return
+
+        row = self.rows[row_key]
+        row[ROW_HOSTNAME] = hostname
+        model.row_changed(row.path, row.iter)
+
     def _build_conn_hint(self, conn):
         hint = conn.get_uri()
         if conn.state == conn.STATE_DISCONNECTED:
@@ -786,6 +804,8 @@ class vmmManager(vmmGObjectUI):
         domtext     = ("<span size='smaller' weight='bold'>%s</span>" %
                        row[ROW_NAME])
         statetext   = "<span size='smaller'>%s</span>" % row[ROW_STATUS]
+        if row[ROW_HOSTNAME]:
+            statetext += ", <span size='smaller'>%s</span>" % row[ROW_HOSTNAME]
         return domtext + "\n" + statetext
 
     def _build_vm_row(self, vm):
@@ -802,6 +822,7 @@ class vmmManager(vmmGObjectUI):
         row.insert(ROW_IS_VM, True)
         row.insert(ROW_IS_VM_RUNNING, vm.is_active())
         row.insert(ROW_COLOR, gtk.gdk.Color(0, 0, 0))
+        row.insert(ROW_HOSTNAME, "")
 
         row[ROW_MARKUP] = self._build_vm_markup(vm, row)
 
@@ -839,6 +860,7 @@ class vmmManager(vmmGObjectUI):
         row.insert(ROW_IS_VM, False)
         row.insert(ROW_IS_VM_RUNNING, False)
         row.insert(ROW_COLOR, self._build_conn_color(conn))
+        row.insert(ROW_HOSTNAME, "")
 
         _iter = model.append(None, row)
         path = model.get_path(_iter)
-- 
1.7.4.4


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]