[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 <[email protected]> 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
_______________________________________________ virt-tools-list mailing list [email protected] https://www.redhat.com/mailman/listinfo/virt-tools-list
