2013-10-27 21:59:46 +01:00
# Copyright (C) 2011, 2013 Red Hat, Inc.
2011-07-18 19:53:54 +01:00
#
2018-04-04 14:35:41 +01:00
# This work is licensed under the GNU GPLv2 or later.
2018-03-20 15:00:02 -04:00
# See the COPYING file in the top-level directory.
2011-07-18 19:53:54 +01:00
2018-03-13 13:00:59 -04:00
import queue
import threading
2011-07-18 19:53:54 +01:00
2019-06-16 21:12:39 -04:00
from virtinst import log
2019-06-16 22:19:17 -04:00
from . . baseclass import vmmGObject
from . . connmanager import vmmConnectionManager
from . . object . domain import vmmInspectionApplication , vmmInspectionData
2011-07-18 19:53:54 +01:00
2013-04-13 14:34:52 -04:00
2018-03-13 16:37:46 -04:00
def _inspection_error ( _errstr ) :
data = vmmInspectionData ( )
data . errorstr = _errstr
return data
2020-08-27 16:37:15 -04:00
def _make_fake_data ( vm ) :
2020-08-26 08:30:05 -04:00
"""
Return fake vmmInspectionData for use with the test driver
"""
2020-08-27 16:37:15 -04:00
if not vm . xmlobj . devices . disk :
return _inspection_error ( " Fake test error no disks " )
2020-08-26 08:30:05 -04:00
data = vmmInspectionData ( )
data . os_type = " test_os_type "
data . distro = " test_distro "
data . major_version = 123
data . minor_version = 456
data . hostname = " test_hostname "
data . product_name = " test_product_name "
data . product_variant = " test_product_variant "
from gi . repository import Gtk
icontheme = Gtk . IconTheme . get_default ( )
icon = icontheme . lookup_icon ( " vm_new " , Gtk . IconSize . LARGE_TOOLBAR , 0 )
data . icon = open ( icon . get_filename ( ) , " rb " ) . read ( )
data . applications = [ ]
for prefix in [ " test_app1_ " , " test_app2_ " ] :
import time
app = vmmInspectionApplication ( )
2020-08-26 18:01:02 -04:00
if " app1 " in prefix :
app . display_name = prefix + " display_name "
app . summary = prefix + " summary- " + str ( time . time ( ) )
else :
app . name = prefix + " name "
app . description = prefix + " description- " + str ( time . time ( ) ) + " \n "
2020-08-26 08:30:05 -04:00
app . epoch = 1
app . version = " 2 "
app . release = " 3 "
data . applications . append ( app )
return data
2020-09-02 15:46:18 -04:00
def _perform_inspection ( conn , vm ) : # pragma: no cover
2020-08-26 08:29:24 -04:00
"""
Perform the actual guestfs interaction and return results in
a vmmInspectionData object
"""
import guestfs # pylint: disable=import-error
g = guestfs . GuestFS ( close_on_exit = False , python_return_dict = True )
prettyvm = conn . get_uri ( ) + " : " + vm . get_name ( )
try :
g . add_libvirt_dom ( vm . get_backend ( ) , readonly = 1 )
g . launch ( )
except Exception as e :
log . debug ( " %s : Error launching libguestfs appliance: %s " ,
prettyvm , str ( e ) )
return _inspection_error (
_ ( " Error launching libguestfs appliance: %s " ) % str ( e ) )
log . debug ( " %s : inspection appliance connected " , prettyvm )
# Inspect the operating system.
roots = g . inspect_os ( )
if len ( roots ) == 0 :
log . debug ( " %s : no operating systems found " , prettyvm )
return _inspection_error (
_ ( " Inspection found no operating systems. " ) )
# Arbitrarily pick the first root device.
root = roots [ 0 ]
# Inspection results.
os_type = 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
product_variant = g . inspect_get_product_variant ( root ) # string
package_format = g . inspect_get_package_format ( root ) # string
# 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
# 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.
mps = g . inspect_get_mountpoints ( root )
mps = sorted ( mps . items ( ) , key = lambda k : len ( k [ 0 ] ) )
for mp , dev in mps :
try :
g . mount_ro ( dev , mp )
filesystems_mounted = True
except Exception :
log . exception ( " %s : exception mounting %s on %s "
" (ignored) " ,
prettyvm , dev , mp )
icon = None
apps = None
if filesystems_mounted :
# string containing PNG data
icon = g . inspect_get_icon ( root , favicon = 0 , highquality = 1 )
if icon is None or len ( icon ) == 0 :
# no high quality icon, try a low quality one
icon = g . inspect_get_icon ( root , favicon = 0 , highquality = 0 )
if icon is None or len ( icon ) == 0 :
icon = None
# Inspection applications.
try :
gapps = g . inspect_list_applications2 ( root )
# applications listing worked, so make apps a real list
# (instead of None)
apps = [ ]
for gapp in gapps :
app = vmmInspectionApplication ( )
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 " ]
apps . append ( app )
except Exception :
log . exception ( " %s : exception while listing apps (ignored) " ,
prettyvm )
# Force the libguestfs handle to close right now.
del g
# Log what we found.
log . debug ( " %s : detected operating system: %s %s %d . %d ( %s ) ( %s ) " ,
prettyvm , os_type , distro , major_version , minor_version ,
product_name , package_format )
log . debug ( " hostname: %s " , hostname )
if icon :
log . debug ( " icon: %d bytes " , len ( icon ) )
if apps :
log . debug ( " # apps: %d " , len ( apps ) )
data = vmmInspectionData ( )
data . os_type = str ( os_type )
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 )
data . icon = icon
data . applications = list ( apps or [ ] )
data . package_format = str ( package_format )
return data
2011-07-22 14:19:35 -04:00
class vmmInspection ( vmmGObject ) :
2018-03-13 13:00:59 -04:00
_libguestfs_installed = None
@classmethod
2018-03-14 13:13:22 -04:00
def get_instance ( cls ) :
2018-03-13 13:00:59 -04:00
if not cls . _instance :
if not cls . libguestfs_installed ( ) :
2020-09-02 15:46:18 -04:00
return None # pragma: no cover
2018-03-17 16:08:20 -04:00
cls . _instance = vmmInspection ( )
2018-03-13 13:00:59 -04:00
return cls . _instance
@classmethod
def libguestfs_installed ( cls ) :
if cls . _libguestfs_installed is None :
try :
import guestfs as ignore # pylint: disable=import-error
2019-06-16 21:12:39 -04:00
log . debug ( " python guestfs is installed " )
2018-03-13 13:00:59 -04:00
cls . _libguestfs_installed = True
2020-09-02 15:46:18 -04:00
except ImportError : # pragma: no cover
2019-06-16 21:12:39 -04:00
log . debug ( " python guestfs is not installed " )
2018-03-13 13:00:59 -04:00
cls . _libguestfs_installed = False
2020-09-02 15:46:18 -04:00
except Exception : # pragma: no cover
2019-06-16 21:12:39 -04:00
log . debug ( " error importing guestfs " ,
2018-03-13 13:00:59 -04:00
exc_info = True )
cls . _libguestfs_installed = False
return cls . _libguestfs_installed
2011-07-22 14:22:18 -04:00
2018-03-14 13:13:22 -04:00
def __init__ ( self ) :
2011-07-22 14:19:35 -04:00
vmmGObject . __init__ ( self )
2018-03-15 07:43:56 -04:00
self . _cleanup_on_app_close ( )
2011-07-22 14:19:35 -04:00
2018-03-13 13:00:59 -04:00
self . _thread = None
2011-07-22 14:19:35 -04:00
2018-03-13 13:00:59 -04:00
self . _q = queue . Queue ( )
2012-01-25 11:52:45 -05:00
self . _cached_data = { }
2020-02-01 15:46:49 -05:00
self . _uris = [ ]
2011-07-18 19:53:54 +01:00
2018-03-13 13:00:59 -04:00
val = self . config . get_libguestfs_inspect_vms ( )
2019-06-16 21:12:39 -04:00
log . debug ( " libguestfs gsetting enabled= %s " , str ( val ) )
2018-03-13 13:00:59 -04:00
if not val :
return
2018-03-14 10:29:20 -04:00
2018-03-14 13:13:22 -04:00
connmanager = vmmConnectionManager . get_instance ( )
2020-02-01 15:46:49 -05:00
connmanager . connect ( " conn-added " , self . _conn_added_cb )
connmanager . connect ( " conn-removed " , self . _conn_removed_cb )
2018-03-14 13:13:22 -04:00
for conn in connmanager . conns . values ( ) :
2020-09-02 15:46:18 -04:00
self . _conn_added_cb ( connmanager , conn ) # pragma: no cover
2018-03-14 10:29:20 -04:00
2018-03-13 13:00:59 -04:00
self . _start ( )
2011-07-23 21:16:54 -04:00
def _cleanup ( self ) :
2018-03-13 13:00:59 -04:00
self . _stop ( )
self . _q = queue . Queue ( )
2012-01-25 11:52:45 -05:00
self . _cached_data = { }
2011-07-22 14:19:35 -04:00
2020-02-01 15:46:49 -05:00
def _conn_added_cb ( self , connmanager , conn ) :
uri = conn . get_uri ( )
if uri in self . _uris :
2020-09-02 15:46:18 -04:00
return # pragma: no cover
2020-02-01 15:46:49 -05:00
self . _uris . append ( uri )
conn . connect ( " vm-added " , self . _vm_added_cb )
2020-09-02 15:46:18 -04:00
for vm in conn . list_vms ( ) : # pragma: no cover
2020-09-01 12:35:26 -04:00
self . _vm_added_cb ( conn , vm . get_name ( ) )
2011-07-18 19:53:54 +01:00
2020-02-01 15:46:49 -05:00
def _conn_removed_cb ( self , connmanager , uri ) :
self . _uris . remove ( uri )
2011-07-18 19:53:54 +01:00
2020-09-01 11:54:10 -04:00
def _vm_added_cb ( self , conn , vm ) :
2020-02-01 15:46:49 -05:00
# Called by the main thread whenever a VM is added to vmlist.
2020-09-01 11:54:10 -04:00
name = vm . get_name ( )
2020-09-02 15:46:18 -04:00
if name . startswith ( " guestfs- " ) : # pragma: no cover
2020-09-01 11:54:10 -04:00
log . debug ( " ignore libvirt/guestfs temporary VM %s " , name )
2017-02-23 11:22:24 +01:00
return
2020-09-01 12:35:26 -04:00
self . _q . put ( ( conn . get_uri ( ) , vm . get_name ( ) ) )
2017-02-23 11:22:25 +01:00
2018-03-13 13:00:59 -04:00
def _start ( self ) :
self . _thread = threading . Thread (
name = " inspection thread " , target = self . _run )
self . _thread . daemon = True
2018-03-13 16:07:23 -04:00
self . _thread . start ( )
2011-07-18 19:53:54 +01:00
2018-03-13 13:00:59 -04:00
def _stop ( self ) :
if self . _thread is None :
return
self . _q . put ( None )
self . _thread = None
2011-07-22 14:19:35 -04:00
def _run ( self ) :
2017-02-23 11:22:23 +01:00
# Process everything on the queue. If the queue is empty when
# called, block.
2011-07-18 19:53:54 +01:00
while True :
2020-02-01 15:46:49 -05:00
data = self . _q . get ( )
if data is None :
log . debug ( " libguestfs queue vm=None, exiting thread " )
2018-03-13 13:00:59 -04:00
return
2020-09-01 12:35:26 -04:00
uri , vmname = data
self . _process_vm ( uri , vmname )
2017-02-23 11:22:23 +01:00
self . _q . task_done ( )
2011-07-18 19:53:54 +01:00
2020-09-01 12:35:26 -04:00
def _process_vm ( self , uri , vmname ) :
2020-02-01 15:46:49 -05:00
connmanager = vmmConnectionManager . get_instance ( )
conn = connmanager . conns . get ( uri )
if not conn :
2020-09-02 15:46:18 -04:00
return # pragma: no cover
2018-03-13 13:00:59 -04:00
2020-09-01 12:35:26 -04:00
vm = conn . get_vm_by_name ( vmname )
2020-02-01 15:46:49 -05:00
if not vm :
2020-09-02 15:46:18 -04:00
return # pragma: no cover
2017-02-23 11:22:19 +01:00
2018-03-13 16:37:46 -04: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 ) :
2020-02-01 15:46:49 -05:00
vm . set_inspection_data ( _data )
2018-03-13 16:37:46 -04:00
self . _cached_data [ vm . get_uuid ( ) ] = _data
2017-02-23 11:22:19 +01:00
2018-03-13 16:37:46 -04:00
prettyvm = conn . get_uri ( ) + " : " + vm . get_name ( )
2017-02-23 11:22:19 +01:00
vmuuid = vm . get_uuid ( )
2018-03-13 16:37:46 -04:00
if vmuuid in self . _cached_data :
data = self . _cached_data . get ( vmuuid )
if vm . inspection != data :
2019-06-16 21:12:39 -04:00
log . debug ( " Found cached data for %s " , prettyvm )
2018-03-13 16:37:46 -04:00
_set_vm_inspection_data ( data )
return
2013-11-26 21:57:06 +01:00
2018-03-13 16:37:46 -04:00
try :
data = self . _inspect_vm ( conn , vm )
2020-09-02 15:46:18 -04:00
except Exception as e : # pragma: no cover
2018-03-13 16:37:46 -04:00
data = _inspection_error ( _ ( " Error inspection VM: %s " ) % str ( e ) )
2019-06-16 21:12:39 -04:00
log . exception ( " %s : exception while processing " , prettyvm )
2011-07-18 19:53:54 +01:00
2018-03-13 16:37:46 -04:00
_set_vm_inspection_data ( data )
2017-02-23 11:22:20 +01:00
def _inspect_vm ( self , conn , vm ) :
2018-03-13 13:00:59 -04:00
if self . _thread is None :
2020-09-02 15:46:18 -04:00
return # pragma: no cover
2011-07-18 17:52:20 -04:00
2020-09-02 15:46:18 -04:00
if conn . is_remote ( ) : # pragma: no cover
2018-03-13 16:37:46 -04:00
return _inspection_error (
_ ( " Cannot inspect VM on remote connection " ) )
if conn . is_test ( ) :
2020-08-27 16:37:15 -04:00
return _make_fake_data ( vm )
2018-03-13 16:37:46 -04:00
2020-09-02 15:46:18 -04:00
return _perform_inspection ( conn , vm ) # pragma: no cover
2020-02-01 15:46:49 -05:00
##############
# Public API #
##############
def vm_refresh ( self , vm ) :
log . debug ( " Refresh requested for vm= %s " , vm . get_name ( ) )
# 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.
self . _cached_data . pop ( vm . get_uuid ( ) , None )
2020-09-01 12:35:26 -04:00
self . _q . put ( ( vm . conn . get_uri ( ) , vm . get_name ( ) ) )