[virt-tools-list] [PATCH 1/4] Add inspection thread.

Cole Robinson crobinso at redhat.com
Mon Jul 18 18:32:42 UTC 2011


On 07/18/2011 10:18 AM, Richard W.M. Jones wrote:
> From: "Richard W.M. Jones" <rjones at redhat.com>
> 
> This commit adds an inspection daemon thread which performs libguestfs
> inspection on domains when they first appear in the manager UI.
> 
> python-guestfs is not required.
> ---
>  src/virtManager/config.py     |   31 ++++++
>  src/virtManager/engine.py     |   17 ++++
>  src/virtManager/inspection.py |  209 +++++++++++++++++++++++++++++++++++++++++
>  src/virtManager/manager.py    |    3 +
>  4 files changed, 260 insertions(+), 0 deletions(-)
>  create mode 100644 src/virtManager/inspection.py
> 
> diff --git a/src/virtManager/config.py b/src/virtManager/config.py
> index d28b083..d7da236 100644
> --- a/src/virtManager/config.py
> +++ b/src/virtManager/config.py
> @@ -104,8 +104,39 @@ class vmmConfig(object):
>          self._objects = []
>  
>          self.support_threading = virtinst.support.support_threading()
> +
> +        self.support_inspection = self.check_inspection(self.support_threading)
> +
>          self._spice_error = None
>  
> +    def check_inspection(self, support_threading):
> +        if not support_threading:
> +            return False
> +
> +        try:
> +            # Check we can open the Python guestfs module.
> +            from guestfs import GuestFS
> +            g = GuestFS()
> +
> +            # Check for the first version which fixed Python GIL bug.
> +            version = g.version()
> +            if version["major"] == 1: # major must be 1
> +                if version["minor"] == 8:
> +                    if version["release"] >= 6: # >= 1.8.6
> +                        return True
> +                elif version["minor"] == 10:
> +                    if version["release"] >= 1: # >= 1.10.1
> +                        return True
> +                elif version["minor"] == 11:
> +                    if version["release"] >= 2: # >= 1.11.2
> +                        return True
> +                elif version["minor"] >= 12:    # >= 1.12, 1.13, etc.
> +                    return True
> +        except:
> +            pass
> +
> +        return False
> +
>      # General app wide helpers (gconf agnostic)
>  
>      def get_shutdown_icon_name(self):
> diff --git a/src/virtManager/engine.py b/src/virtManager/engine.py
> index dbbb8bd..a310b8e 100644
> --- a/src/virtManager/engine.py
> +++ b/src/virtManager/engine.py
> @@ -236,6 +236,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
> @@ -531,6 +534,20 @@ class vmmEngine(vmmGObject):
>          logging.debug("Exiting app normally.")
>          gtk.main_quit()
>  
> +    def _create_inspection_thread(self):
> +        if not self.config.support_inspection:
> +            logging.debug("No inspection thread because "
> +                          "libguestfs is too old, not available, "
> +                          "or libvirt is not thread safe.")
> +            return
> +        from virtManager.inspection import vmmInspection
> +        self.inspection_thread = vmmInspection()
> +        self.inspection_thread.daemon = True
> +        self.inspection_thread.start()
> +        self.connect("connection-added", self.inspection_thread.conn_added)
> +        self.connect("connection-removed", self.inspection_thread.conn_removed)
> +        return
> +
>      def add_connection(self, uri):
>          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..91536bf
> --- /dev/null
> +++ b/src/virtManager/inspection.py
> @@ -0,0 +1,209 @@
> +#
> +# 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
> +from Queue import Queue, Empty
> +from threading import Thread
> +
> +from guestfs import GuestFS
> +
> +import logging
> +import util
> +
> +class vmmInspection(Thread):
> +    _name = "inspection thread"
> +    _wait = 15 # seconds
> +
> +    def __init__(self):
> +        Thread.__init__(self, name=self._name)
> +        self._q = Queue()
> +        self._conns = dict()
> +        self._vmseen = dict()
> +
> +    # Called by the main thread whenever a connection is added or
> +    # removed.  We tell the inspection thread, so it can track
> +    # connections.
> +    def conn_added(self, engine_ignore, conn):
> +        obj = ("conn_added", conn)
> +        self._q.put(obj)
> +
> +    def conn_removed(self, engine_ignore, uri):
> +        obj = ("conn_removed", uri)
> +        self._q.put(obj)
> +
> +    # Called by the main thread whenever a VM is added to vmlist.
> +    def vm_added(self):
> +        obj = ("vm_added")
> +        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)
> +
> +        while True:
> +            logging.debug("%s: ready" % self._name)
> +            self._process_queue()
> +            self._process_vms()
> +
> +    # Process everything on the queue.  If the queue is empty when
> +    # called, block.
> +    def _process_queue(self):
> +        first_obj = self._q.get()
> +        self._process_queue_item(first_obj)
> +        self._q.task_done()
> +        try:
> +            while True:
> +                obj = self._q.get(False)
> +                self._process_queue_item(obj)
> +                self._q.task_done()
> +        except Empty:
> +            pass
> +
> +    def _process_queue_item(self, obj):
> +        if obj[0] == "conn_added":
> +            conn = obj[1]
> +            if conn and not (conn.is_remote()):
> +                uri = conn.get_uri()
> +                self._conns[uri] = conn
> +        elif obj[0] == "conn_removed":
> +            uri = obj[1]
> +            del self._conns[uri]
> +        elif obj[0] == "vm_added":
> +            # Nothing - just a signal for the inspection thread to wake up.
> +            pass
> +
> +    # Any VMs we've not seen yet?  If so, process them.
> +    def _process_vms(self):
> +        for uri, conn in self._conns.iteritems():
> +            for vmuuid in conn.list_vm_uuids():
> +                if not (vmuuid in self._vmseen):
> +                    logging.debug("%s: processing started on '%s'" %
> +                                  (self._name, vmuuid))
> +                    try:
> +                        vm = conn.get_vm(vmuuid)
> +                        # Whether success or failure, we've "seen" this VM now.
> +                        self._vmseen[vmuuid] = True
> +                        self._process(conn, vm, vmuuid)
> +                    except:
> +                        logging.exception("%s: exception while processing '%s'" %
> +                                          (self._name, vmuuid))
> +                    logging.debug("%s: processing done on '%s'" %
> +                                  (self._name, vmuuid))
> +
> +    def _process(self, conn, vm, vmuuid):
> +        xml = vm.get_xml()
> +
> +        # Add the disks.  Note they *must* be added with readonly flag set.
> +        g = GuestFS()
> +        for disk in vm.get_disk_devices():
> +            path = disk.path
> +            driver_type = disk.driver_type
> +            if (disk.type == "block" or disk.type == "file") and path != None:
> +                g.add_drive_opts(path, readonly=1, format=driver_type)
> +
> +        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 results.
> +        typ = g.inspect_get_type(root) # eg. "linux"
> +        distro = g.inspect_get_distro(root) # eg. "fedora"
> +        major_version = g.inspect_get_major_version(root) # eg. 14
> +        minor_version = g.inspect_get_minor_version(root) # eg. 0
> +        hostname = g.inspect_get_hostname(root) # string
> +        product_name = g.inspect_get_product_name(root) # string
> +
> +        # Added in libguestfs 1.9.13:
> +        try:
> +            product_variant = g.inspect_get_product_variant(root) # string
> +        except AttributeError:
> +            pass
> +

product_variant will be unset if this function raises attributeerror. You'll
need to just product_variant = None before hand.

Additionally, rather than do the exception check, I'd prefer something
explicit like

if hasattr(g, "inspect_get_product_variant")

since the bindings could conceivably raise an attribute error deeper in the
call and we would false positive.

As a general note, this series raised some warnings from 'make check-pylint'.
Please make sure pylint and python-pep8 are installed, and run that command
before submitting. It's okay if each individual patch isn't clean, since this
patch will give some 'unused variable' warnings until patch 2.

Also if there are warnings in other parts of the code, ignore them. I haven't
run pylint against the code base on f15 yet.

Thanks,
Cole




More information about the virt-tools-list mailing list