2011-07-18 22:53:54 +04:00
#
2013-10-28 00:59:46 +04:00
# Copyright (C) 2011, 2013 Red Hat, Inc.
2011-07-18 22:53:54 +04:00
#
# 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.
#
2017-10-11 14:35:53 +03:00
from queue import Queue
2011-07-18 22:53:54 +04:00
from threading import Thread
2011-07-22 22:19:35 +04:00
import logging
2011-07-18 22:53:54 +04:00
2014-04-03 02:39:43 +04:00
from guestfs import GuestFS # pylint: disable=import-error
2011-07-18 22:53:54 +04:00
2014-09-13 00:10:45 +04:00
from . baseclass import vmmGObject
from . domain import vmmInspectionData
2011-07-18 22:53:54 +04:00
2013-04-13 22:34:52 +04:00
2011-07-22 22:19:35 +04:00
class vmmInspection ( vmmGObject ) :
2011-07-22 22:22:18 +04:00
# Can't find a way to make Thread release our reference
_leak_check = False
2011-07-18 22:53:54 +04:00
def __init__ ( self ) :
2011-07-22 22:19:35 +04:00
vmmGObject . __init__ ( self )
self . _thread = Thread ( name = " inspection thread " , target = self . _run )
self . _thread . daemon = True
2013-06-14 00:01:25 +04:00
self . _wait = 5 * 1000 # 5 seconds
2011-07-22 22:19:35 +04:00
2011-07-18 22:53:54 +04:00
self . _q = Queue ( )
2012-01-25 20:52:45 +04:00
self . _conns = { }
self . _vmseen = { }
self . _cached_data = { }
2011-07-18 22:53:54 +04:00
2011-07-24 05:16:54 +04:00
def _cleanup ( self ) :
self . _thread = None
self . _q = Queue ( )
self . _conns = { }
self . _vmseen = { }
2012-01-25 20:52:45 +04:00
self . _cached_data = { }
2011-07-22 22:19:35 +04:00
2011-07-18 22:53:54 +04:00
# 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.
2014-06-03 01:17:47 +04:00
def vm_added ( self , conn , connkey ) :
2017-02-23 13:22:24 +03:00
if connkey . startswith ( " guestfs- " ) :
logging . debug ( " ignore libvirt/guestfs temporary VM %s " ,
connkey )
return
2017-02-23 13:22:21 +03:00
obj = ( " vm_added " , conn . get_uri ( ) , connkey )
2011-07-18 22:53:54 +04:00
self . _q . put ( obj )
2017-02-23 13:22:25 +03:00
def vm_refresh ( self , vm ) :
obj = ( " vm_refresh " , vm . conn . get_uri ( ) , vm . get_name ( ) , vm . get_uuid ( ) )
self . _q . put ( obj )
2011-07-22 22:19:35 +04:00
def start ( self ) :
2011-07-18 22:53:54 +04:00
# 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).
2011-07-22 22:19:35 +04:00
def cb ( ) :
self . _thread . start ( )
return 0
2011-07-19 02:10:54 +04:00
logging . debug ( " waiting " )
2012-11-08 17:15:02 +04:00
self . timeout_add ( self . _wait , cb )
2011-07-18 22:53:54 +04:00
2011-07-22 22:19:35 +04:00
def _run ( self ) :
2017-02-23 13:22:23 +03:00
# Process everything on the queue. If the queue is empty when
# called, block.
2011-07-18 22:53:54 +04:00
while True :
2017-02-23 13:22:23 +03:00
obj = self . _q . get ( )
self . _process_queue_item ( obj )
self . _q . task_done ( )
2011-07-18 22:53:54 +04:00
def _process_queue_item ( self , obj ) :
if obj [ 0 ] == " conn_added " :
conn = obj [ 1 ]
2017-02-23 13:22:22 +03:00
uri = conn . get_uri ( )
if conn and not ( conn . is_remote ( ) ) and not ( uri in self . _conns ) :
2011-07-18 22:53:54 +04:00
self . _conns [ uri ] = conn
2011-07-22 21:42:20 +04:00
conn . connect ( " vm-added " , self . vm_added )
2018-03-13 19:34:26 +03:00
for vm in conn . list_vms ( ) :
self . vm_added ( conn , vm . get_connkey ( ) )
2011-07-18 22:53:54 +04:00
elif obj [ 0 ] == " conn_removed " :
uri = obj [ 1 ]
del self . _conns [ uri ]
2017-02-23 13:22:25 +03:00
elif obj [ 0 ] == " vm_added " or obj [ 0 ] == " vm_refresh " :
2017-02-23 13:22:21 +03:00
uri = obj [ 1 ]
if not ( uri in self . _conns ) :
# This connection disappeared in the meanwhile.
return
conn = self . _conns [ uri ]
if not conn . is_active ( ) :
return
connkey = obj [ 2 ]
vm = conn . get_vm ( connkey )
if not vm :
# The VM was removed in the meanwhile.
return
2017-02-23 13:22:25 +03:00
if obj [ 0 ] == " vm_refresh " :
vmuuid = obj [ 3 ]
# When refreshing the inspection data of a VM,
# all we need is to remove it from the "seen" cache,
# as the data itself will be replaced once the new
# results are available.
del self . _vmseen [ vmuuid ]
2017-02-23 13:22:21 +03:00
self . _process_vm ( conn , vm )
2017-02-23 13:22:19 +03:00
# Try processing a single VM, keeping into account whether it was
# visited already, and whether there are cached data for it.
def _process_vm ( self , conn , vm ) :
def set_inspection_error ( vm ) :
data = vmmInspectionData ( )
data . error = True
self . _set_vm_inspection_data ( vm , data )
vmuuid = vm . get_uuid ( )
prettyvm = vmuuid
try :
prettyvm = conn . get_uri ( ) + " : " + vm . get_name ( )
if vmuuid in self . _vmseen :
data = self . _cached_data . get ( vmuuid )
if not data :
return
if vm . inspection != data :
logging . debug ( " Found cached data for %s " , prettyvm )
2013-11-27 00:57:06 +04:00
self . _set_vm_inspection_data ( vm , data )
2017-02-23 13:22:19 +03:00
return
2013-11-27 00:57:06 +04:00
2017-02-23 13:22:19 +03:00
# Whether success or failure, we've "seen" this VM now.
self . _vmseen [ vmuuid ] = True
try :
2017-02-23 13:22:20 +03:00
data = self . _inspect_vm ( conn , vm )
2017-02-23 13:22:19 +03:00
if data :
self . _set_vm_inspection_data ( vm , data )
else :
set_inspection_error ( vm )
2017-07-24 11:26:48 +03:00
except Exception :
2017-02-23 13:22:19 +03:00
set_inspection_error ( vm )
raise
2017-07-24 11:26:48 +03:00
except Exception :
2017-02-23 13:22:19 +03:00
logging . exception ( " %s : exception while processing " , prettyvm )
2011-07-18 22:53:54 +04:00
2017-02-23 13:22:20 +03:00
def _inspect_vm ( self , conn , vm ) :
2013-12-19 01:34:10 +04:00
g = GuestFS ( close_on_exit = False )
2011-07-19 02:10:54 +04:00
prettyvm = conn . get_uri ( ) + " : " + vm . get_name ( )
2011-07-19 01:52:20 +04:00
2015-01-08 17:12:01 +03:00
g . add_libvirt_dom ( vm . get_backend ( ) , readonly = 1 )
2011-07-18 22:53:54 +04:00
g . launch ( )
# Inspect the operating system.
roots = g . inspect_os ( )
if len ( roots ) == 0 :
2011-07-19 02:10:54 +04:00
logging . debug ( " %s : no operating systems found " , prettyvm )
2013-11-27 00:57:06 +04:00
return None
2011-07-18 22:53:54 +04:00
# Arbitrarily pick the first root device.
root = roots [ 0 ]
# Inspection results.
2017-02-08 18:55:33 +03:00
os_type = g . inspect_get_type ( root ) # eg. "linux"
2013-04-13 22:34:52 +04:00
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
2013-06-14 00:00:33 +04:00
product_variant = g . inspect_get_product_variant ( root ) # string
2011-07-18 22:53:54 +04:00
# For inspect_list_applications and inspect_get_icon we
# require that the guest filesystems are mounted. However
# don't fail if this is not possible (I'm looking at you,
# FreeBSD).
filesystems_mounted = False
try :
# Mount up the disks, like guestfish --ro -i.
# Sort keys by length, shortest first, so that we end up
# mounting the filesystems in the correct order.
2013-06-14 00:12:55 +04:00
mps = list ( g . inspect_get_mountpoints ( root ) )
2011-07-18 22:53:54 +04:00
def compare ( a , b ) :
if len ( a [ 0 ] ) > len ( b [ 0 ] ) :
return 1
elif len ( a [ 0 ] ) == len ( b [ 0 ] ) :
return 0
else :
return - 1
mps . sort ( compare )
for mp_dev in mps :
try :
g . mount_ro ( mp_dev [ 1 ] , mp_dev [ 0 ] )
2017-07-24 11:26:48 +03:00
except Exception :
2012-01-25 20:52:45 +04:00
logging . exception ( " %s : exception mounting %s on %s "
" (ignored) " ,
prettyvm , mp_dev [ 1 ] , mp_dev [ 0 ] )
2011-07-18 22:53:54 +04:00
filesystems_mounted = True
2017-07-24 11:26:48 +03:00
except Exception :
2012-01-25 20:52:45 +04:00
logging . exception ( " %s : exception while mounting disks (ignored) " ,
prettyvm )
2011-07-18 22:53:54 +04:00
icon = None
apps = None
if filesystems_mounted :
2013-06-14 00:00:33 +04:00
# string containing PNG data
icon = g . inspect_get_icon ( root , favicon = 0 , highquality = 1 )
2017-02-08 18:55:36 +03:00
if icon == " " or icon is None :
# no high quality icon, try a low quality one
icon = g . inspect_get_icon ( root , favicon = 0 , highquality = 0 )
if icon == " " :
icon = None
2011-07-18 22:53:54 +04:00
# Inspection applications.
apps = g . inspect_list_applications ( root )
# Force the libguestfs handle to close right now.
del g
# Log what we found.
2012-01-25 20:52:45 +04:00
logging . debug ( " %s : detected operating system: %s %s %d . %d ( %s ) " ,
2017-02-08 18:55:33 +03:00
prettyvm , os_type , distro , major_version , minor_version ,
2012-01-25 20:52:45 +04:00
product_name )
2011-07-19 02:10:54 +04:00
logging . debug ( " hostname: %s " , hostname )
2011-07-18 22:53:54 +04:00
if icon :
2011-07-19 02:10:54 +04:00
logging . debug ( " icon: %d bytes " , len ( icon ) )
2011-07-18 22:53:54 +04:00
if apps :
2011-07-19 02:10:54 +04:00
logging . debug ( " # apps: %d " , len ( apps ) )
2011-07-22 22:19:35 +04:00
2012-01-25 20:52:45 +04:00
data = vmmInspectionData ( )
2017-02-08 18:55:33 +03:00
data . os_type = str ( os_type )
2012-01-25 20:52:45 +04:00
data . distro = str ( distro )
data . major_version = int ( major_version )
data . minor_version = int ( minor_version )
data . hostname = str ( hostname )
data . product_name = str ( product_name )
data . product_variant = str ( product_variant )
2013-06-14 00:10:28 +04:00
data . icon = icon
2012-01-25 20:52:45 +04:00
data . applications = list ( apps )
2013-11-27 00:57:06 +04:00
data . error = False
2012-01-25 20:52:45 +04:00
2013-11-27 00:57:06 +04:00
return data
2012-01-25 20:52:45 +04:00
def _set_vm_inspection_data ( self , vm , data ) :
vm . inspection = data
vm . inspection_data_updated ( )
self . _cached_data [ vm . get_uuid ( ) ] = data