2013-10-28 00:59:46 +04:00
# Copyright (C) 2011, 2013 Red Hat, Inc.
2011-07-18 22:53:54 +04:00
#
2018-04-04 16:35:41 +03:00
# This work is licensed under the GNU GPLv2 or later.
2018-03-20 22:00:02 +03:00
# See the COPYING file in the top-level directory.
2011-07-18 22:53:54 +04:00
2011-07-22 22:19:35 +04:00
import logging
2018-03-13 20:00:59 +03:00
import queue
import threading
2011-07-18 22:53:54 +04:00
2014-09-13 00:10:45 +04:00
from . baseclass import vmmGObject
2018-03-14 20:13:22 +03:00
from . connmanager import vmmConnectionManager
2019-04-18 17:08:30 +03:00
from . domain import vmmInspectionApplication , vmmInspectionData
2011-07-18 22:53:54 +04:00
2013-04-13 22:34:52 +04:00
2018-03-13 23:37:46 +03:00
def _inspection_error ( _errstr ) :
data = vmmInspectionData ( )
data . errorstr = _errstr
return data
2011-07-22 22:19:35 +04:00
class vmmInspection ( vmmGObject ) :
2018-03-13 20:00:59 +03:00
_libguestfs_installed = None
@classmethod
2018-03-14 20:13:22 +03:00
def get_instance ( cls ) :
2018-03-13 20:00:59 +03:00
if not cls . _instance :
if not cls . libguestfs_installed ( ) :
return None
2018-03-17 23:08:20 +03:00
cls . _instance = vmmInspection ( )
2018-03-13 20:00:59 +03:00
return cls . _instance
@classmethod
def libguestfs_installed ( cls ) :
if cls . _libguestfs_installed is None :
try :
import guestfs as ignore # pylint: disable=import-error
logging . debug ( " python guestfs is installed " )
cls . _libguestfs_installed = True
except ImportError :
logging . debug ( " python guestfs is not installed " )
cls . _libguestfs_installed = False
except Exception :
logging . debug ( " error importing guestfs " ,
exc_info = True )
cls . _libguestfs_installed = False
return cls . _libguestfs_installed
2011-07-22 22:22:18 +04:00
2018-03-14 20:13:22 +03:00
def __init__ ( self ) :
2011-07-22 22:19:35 +04:00
vmmGObject . __init__ ( self )
2018-03-15 14:43:56 +03:00
self . _cleanup_on_app_close ( )
2011-07-22 22:19:35 +04:00
2018-03-13 20:00:59 +03:00
self . _thread = None
2011-07-22 22:19:35 +04:00
2018-03-13 20:00:59 +03:00
self . _q = queue . Queue ( )
2012-01-25 20:52:45 +04:00
self . _conns = { }
self . _cached_data = { }
2011-07-18 22:53:54 +04:00
2018-03-13 20:00:59 +03:00
val = self . config . get_libguestfs_inspect_vms ( )
logging . debug ( " libguestfs gsetting enabled= %s " , str ( val ) )
if not val :
return
2018-03-14 17:29:20 +03:00
2018-03-14 20:13:22 +03:00
connmanager = vmmConnectionManager . get_instance ( )
connmanager . connect ( " conn-added " , self . _conn_added )
connmanager . connect ( " conn-removed " , self . _conn_removed )
for conn in connmanager . conns . values ( ) :
self . _conn_added ( connmanager , conn )
2018-03-14 17:29:20 +03:00
2018-03-13 20:00:59 +03:00
self . _start ( )
2011-07-24 05:16:54 +04:00
def _cleanup ( self ) :
2018-03-13 20:00:59 +03:00
self . _stop ( )
self . _q = queue . Queue ( )
2011-07-24 05:16:54 +04:00
self . _conns = { }
2012-01-25 20:52:45 +04:00
self . _cached_data = { }
2011-07-22 22:19:35 +04:00
2018-03-14 20:13:22 +03:00
def _conn_added ( self , _src , conn ) :
2011-07-18 22:53:54 +04:00
obj = ( " conn_added " , conn )
self . _q . put ( obj )
2018-03-14 20:13:22 +03:00
def _conn_removed ( self , _src , uri ) :
2011-07-18 22:53:54 +04:00
obj = ( " conn_removed " , uri )
self . _q . put ( obj )
# Called by the main thread whenever a VM is added to vmlist.
2018-03-13 20:00:59 +03: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 ) :
2018-03-15 00:18:42 +03:00
logging . debug ( " Refresh requested for vm= %s " , vm . get_name ( ) )
2017-02-23 13:22:25 +03:00
obj = ( " vm_refresh " , vm . conn . get_uri ( ) , vm . get_name ( ) , vm . get_uuid ( ) )
self . _q . put ( obj )
2018-03-13 20:00:59 +03:00
def _start ( self ) :
self . _thread = threading . Thread (
name = " inspection thread " , target = self . _run )
self . _thread . daemon = True
2018-03-13 23:07:23 +03:00
self . _thread . start ( )
2011-07-18 22:53:54 +04:00
2018-03-13 20:00:59 +03:00
def _stop ( self ) :
if self . _thread is None :
return
self . _q . put ( None )
self . _thread = None
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 ( )
2018-03-13 20:00:59 +03:00
if obj is None :
logging . debug ( " libguestfs queue obj=None, exiting thread " )
return
2017-02-23 13:22:23 +03:00
self . _process_queue_item ( obj )
self . _q . task_done ( )
2011-07-18 22:53:54 +04:00
def _process_queue_item ( self , obj ) :
2018-03-13 20:00:59 +03:00
cmd = obj [ 0 ]
if cmd == " conn_added " :
2011-07-18 22:53:54 +04:00
conn = obj [ 1 ]
2017-02-23 13:22:22 +03:00
uri = conn . get_uri ( )
2018-03-13 23:37:46 +03:00
if uri in self . _conns :
2018-03-13 20:00:59 +03:00
return
self . _conns [ uri ] = conn
conn . connect ( " vm-added " , self . _vm_added )
for vm in conn . list_vms ( ) :
self . _vm_added ( conn , vm . get_connkey ( ) )
elif cmd == " conn_removed " :
2011-07-18 22:53:54 +04:00
uri = obj [ 1 ]
2018-03-13 20:00:59 +03:00
self . _conns . pop ( uri )
elif cmd == " vm_added " or cmd == " vm_refresh " :
2017-02-23 13:22:21 +03:00
uri = obj [ 1 ]
2018-03-13 20:00:59 +03:00
if uri not in self . _conns :
2017-02-23 13:22:21 +03:00
# This connection disappeared in the meanwhile.
return
2018-03-13 20:00:59 +03:00
2017-02-23 13:22:21 +03:00
conn = self . _conns [ uri ]
2018-03-13 20:00:59 +03:00
vm = conn . get_vm ( obj [ 2 ] )
2017-02-23 13:22:21 +03:00
if not vm :
# The VM was removed in the meanwhile.
return
2018-03-13 20:00:59 +03:00
if cmd == " vm_refresh " :
2017-02-23 13:22:25 +03:00
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.
2018-03-13 23:37:46 +03:00
self . _cached_data . pop ( vmuuid , None )
2018-03-13 20:00:59 +03:00
2017-02-23 13:22:21 +03:00
self . _process_vm ( conn , vm )
2017-02-23 13:22:19 +03:00
def _process_vm ( self , conn , vm ) :
2018-03-13 23:37:46 +03:00
# Try processing a single VM, keeping into account whether it was
# visited already, and whether there are cached data for it.
def _set_vm_inspection_data ( _data ) :
vm . inspection = _data
vm . inspection_data_updated ( )
self . _cached_data [ vm . get_uuid ( ) ] = _data
2017-02-23 13:22:19 +03:00
2018-03-13 23:37:46 +03:00
prettyvm = conn . get_uri ( ) + " : " + vm . get_name ( )
2017-02-23 13:22:19 +03:00
vmuuid = vm . get_uuid ( )
2018-03-13 23:37:46 +03:00
if vmuuid in self . _cached_data :
data = self . _cached_data . get ( vmuuid )
if vm . inspection != data :
logging . debug ( " Found cached data for %s " , prettyvm )
_set_vm_inspection_data ( data )
return
2013-11-27 00:57:06 +04:00
2018-03-13 23:37:46 +03:00
try :
data = self . _inspect_vm ( conn , vm )
except Exception as e :
data = _inspection_error ( _ ( " Error inspection VM: %s " ) % str ( e ) )
2017-02-23 13:22:19 +03:00
logging . exception ( " %s : exception while processing " , prettyvm )
2011-07-18 22:53:54 +04:00
2018-03-13 23:37:46 +03:00
_set_vm_inspection_data ( data )
2017-02-23 13:22:20 +03:00
def _inspect_vm ( self , conn , vm ) :
2018-03-13 20:00:59 +03:00
if self . _thread is None :
return
2011-07-19 01:52:20 +04:00
2018-03-13 23:37:46 +03:00
if conn . is_remote ( ) :
return _inspection_error (
_ ( " Cannot inspect VM on remote connection " ) )
if conn . is_test ( ) :
return _inspection_error ( " Cannot inspect VM on test connection " )
2018-03-13 20:00:59 +03:00
import guestfs # pylint: disable=import-error
2011-07-18 22:53:54 +04:00
2019-04-17 19:49:49 +03:00
g = guestfs . GuestFS ( close_on_exit = False , python_return_dict = True )
2018-03-13 20:00:59 +03:00
prettyvm = conn . get_uri ( ) + " : " + vm . get_name ( )
try :
g . add_libvirt_dom ( vm . get_backend ( ) , readonly = 1 )
g . launch ( )
except Exception as e :
logging . debug ( " %s : Error launching libguestfs appliance: %s " ,
prettyvm , str ( e ) )
2018-03-13 23:37:46 +03:00
return _inspection_error (
_ ( " Error launching libguestfs appliance: %s " ) % str ( e ) )
2018-03-13 20:00:59 +03:00
logging . debug ( " %s : inspection appliance connected " , prettyvm )
2011-07-18 22:53:54 +04:00
# 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 )
2018-03-13 23:37:46 +03:00
return _inspection_error (
_ ( " Inspection found no operating systems. " ) )
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
2019-04-17 19:49:51 +03:00
package_format = g . inspect_get_package_format ( 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
2019-04-17 19:49:48 +03:00
# 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.
2019-04-17 19:49:49 +03:00
mps = g . inspect_get_mountpoints ( root )
mps = sorted ( mps . items ( ) , key = lambda k : len ( k [ 0 ] ) )
for mp , dev in mps :
2019-04-17 19:49:48 +03:00
try :
2019-04-17 19:49:49 +03:00
g . mount_ro ( dev , mp )
2019-04-17 19:49:50 +03:00
filesystems_mounted = True
2019-04-17 19:49:48 +03:00
except Exception :
logging . exception ( " %s : exception mounting %s on %s "
" (ignored) " ,
2019-04-17 19:49:49 +03:00
prettyvm , dev , mp )
2019-04-17 19:49:48 +03:00
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 )
2019-01-30 20:28:14 +03:00
if icon is None or len ( icon ) == 0 :
2017-02-08 18:55:36 +03:00
# no high quality icon, try a low quality one
icon = g . inspect_get_icon ( root , favicon = 0 , highquality = 0 )
2019-01-30 20:28:14 +03:00
if icon is None or len ( icon ) == 0 :
2017-02-08 18:55:36 +03:00
icon = None
2011-07-18 22:53:54 +04:00
# Inspection applications.
2019-02-05 20:17:42 +03:00
try :
2019-04-18 17:08:31 +03:00
gapps = g . inspect_list_applications2 ( root )
2019-04-18 17:08:30 +03:00
# applications listing worked, so make apps a real list
# (instead of None)
apps = [ ]
for gapp in gapps :
app = vmmInspectionApplication ( )
2019-04-18 17:08:31 +03:00
if gapp [ " app2_name " ] :
app . name = gapp [ " app2_name " ]
if gapp [ " app2_display_name " ] :
app . display_name = gapp [ " app2_display_name " ]
app . epoch = gapp [ " app2_epoch " ]
if gapp [ " app2_version " ] :
app . version = gapp [ " app2_version " ]
if gapp [ " app2_release " ] :
app . release = gapp [ " app2_release " ]
if gapp [ " app2_summary " ] :
app . summary = gapp [ " app2_summary " ]
if gapp [ " app2_description " ] :
app . description = gapp [ " app2_description " ]
2019-04-18 17:08:30 +03:00
apps . append ( app )
2019-02-05 20:17:42 +03:00
except Exception :
logging . exception ( " %s : exception while listing apps (ignored) " ,
prettyvm )
2011-07-18 22:53:54 +04:00
# Force the libguestfs handle to close right now.
del g
# Log what we found.
2019-04-17 19:49:51 +03:00
logging . debug ( " %s : detected operating system: %s %s %d . %d ( %s ) ( %s ) " ,
2017-02-08 18:55:33 +03:00
prettyvm , os_type , distro , major_version , minor_version ,
2019-04-17 19:49:51 +03:00
product_name , package_format )
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
2018-03-13 20:00:59 +03:00
data . applications = list ( apps or [ ] )
2019-04-17 19:49:51 +03:00
data . package_format = str ( package_format )
2012-01-25 20:52:45 +04:00
2013-11-27 00:57:06 +04:00
return data