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
2018-03-13 20:00:59 +03:00
import queue
import threading
2011-07-18 22:53:54 +04:00
2019-06-17 04:12:39 +03:00
from virtinst import log
2019-06-17 05:19:17 +03:00
from . . baseclass import vmmGObject
from . . connmanager import vmmConnectionManager
from . . object . 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
2020-08-27 23:37:15 +03:00
def _make_fake_data ( vm ) :
2020-08-26 15:30:05 +03:00
"""
Return fake vmmInspectionData for use with the test driver
"""
2020-08-27 23:37:15 +03:00
if not vm . xmlobj . devices . disk :
return _inspection_error ( " Fake test error no disks " )
2020-08-26 15:30:05 +03: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-27 01:01:02 +03: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 15:30:05 +03:00
app . epoch = 1
app . version = " 2 "
app . release = " 3 "
data . applications . append ( app )
return data
2020-09-02 22:46:18 +03:00
def _perform_inspection ( conn , vm ) : # pragma: no cover
2020-08-26 15:29:24 +03: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 :
2023-08-30 11:03:15 +03:00
log . exception ( " %s : exception mounting %s on %s (ignored) " ,
prettyvm , dev , mp )
2020-08-26 15:29:24 +03:00
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) " ,
2023-08-30 11:03:15 +03:00
prettyvm )
2020-08-26 15:29:24 +03:00
# 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 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 ( ) :
2020-09-02 22:46:18 +03:00
return None # pragma: no cover
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
2019-06-17 04:12:39 +03:00
log . debug ( " python guestfs is installed " )
2018-03-13 20:00:59 +03:00
cls . _libguestfs_installed = True
2020-09-02 22:46:18 +03:00
except ImportError : # pragma: no cover
2019-06-17 04:12:39 +03:00
log . debug ( " python guestfs is not installed " )
2018-03-13 20:00:59 +03:00
cls . _libguestfs_installed = False
2020-09-02 22:46:18 +03:00
except Exception : # pragma: no cover
2019-06-17 04:12:39 +03:00
log . debug ( " error importing guestfs " ,
2018-03-13 20:00:59 +03:00
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 . _cached_data = { }
2020-02-01 23:46:49 +03:00
self . _uris = [ ]
2011-07-18 22:53:54 +04:00
2018-03-13 20:00:59 +03:00
val = self . config . get_libguestfs_inspect_vms ( )
2019-06-17 04:12:39 +03:00
log . debug ( " libguestfs gsetting enabled= %s " , str ( val ) )
2018-03-13 20:00:59 +03:00
if not val :
return
2018-03-14 17:29:20 +03:00
2018-03-14 20:13:22 +03:00
connmanager = vmmConnectionManager . get_instance ( )
2020-02-01 23:46:49 +03:00
connmanager . connect ( " conn-added " , self . _conn_added_cb )
connmanager . connect ( " conn-removed " , self . _conn_removed_cb )
2018-03-14 20:13:22 +03:00
for conn in connmanager . conns . values ( ) :
2020-09-02 22:46:18 +03:00
self . _conn_added_cb ( connmanager , conn ) # pragma: no cover
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 ( )
2012-01-25 20:52:45 +04:00
self . _cached_data = { }
2011-07-22 22:19:35 +04:00
2020-02-01 23:46:49 +03:00
def _conn_added_cb ( self , connmanager , conn ) :
uri = conn . get_uri ( )
if uri in self . _uris :
2020-09-02 22:46:18 +03:00
return # pragma: no cover
2020-02-01 23:46:49 +03:00
self . _uris . append ( uri )
conn . connect ( " vm-added " , self . _vm_added_cb )
2020-09-02 22:46:18 +03:00
for vm in conn . list_vms ( ) : # pragma: no cover
2020-09-01 19:35:26 +03:00
self . _vm_added_cb ( conn , vm . get_name ( ) )
2011-07-18 22:53:54 +04:00
2020-02-01 23:46:49 +03:00
def _conn_removed_cb ( self , connmanager , uri ) :
self . _uris . remove ( uri )
2011-07-18 22:53:54 +04:00
2020-09-01 18:54:10 +03:00
def _vm_added_cb ( self , conn , vm ) :
2020-02-01 23:46:49 +03:00
# Called by the main thread whenever a VM is added to vmlist.
2020-09-01 18:54:10 +03:00
name = vm . get_name ( )
2020-09-02 22:46:18 +03:00
if name . startswith ( " guestfs- " ) : # pragma: no cover
2020-09-01 18:54:10 +03:00
log . debug ( " ignore libvirt/guestfs temporary VM %s " , name )
2017-02-23 13:22:24 +03:00
return
2020-09-01 19:35:26 +03:00
self . _q . put ( ( conn . get_uri ( ) , vm . get_name ( ) ) )
2017-02-23 13:22:25 +03:00
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 :
2020-02-01 23:46:49 +03:00
data = self . _q . get ( )
if data is None :
log . debug ( " libguestfs queue vm=None, exiting thread " )
2018-03-13 20:00:59 +03:00
return
2020-09-01 19:35:26 +03:00
uri , vmname = data
self . _process_vm ( uri , vmname )
2017-02-23 13:22:23 +03:00
self . _q . task_done ( )
2011-07-18 22:53:54 +04:00
2020-09-01 19:35:26 +03:00
def _process_vm ( self , uri , vmname ) :
2020-02-01 23:46:49 +03:00
connmanager = vmmConnectionManager . get_instance ( )
conn = connmanager . conns . get ( uri )
if not conn :
2020-09-02 22:46:18 +03:00
return # pragma: no cover
2018-03-13 20:00:59 +03:00
2020-09-01 19:35:26 +03:00
vm = conn . get_vm_by_name ( vmname )
2020-02-01 23:46:49 +03:00
if not vm :
2020-09-02 22:46:18 +03:00
return # pragma: no cover
2017-02-23 13:22:19 +03:00
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 ) :
2020-02-01 23:46:49 +03:00
vm . set_inspection_data ( _data )
2018-03-13 23:37:46 +03:00
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 :
2019-06-17 04:12:39 +03:00
log . debug ( " Found cached data for %s " , prettyvm )
2018-03-13 23:37:46 +03:00
_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 )
2020-09-02 22:46:18 +03:00
except Exception as e : # pragma: no cover
2018-03-13 23:37:46 +03:00
data = _inspection_error ( _ ( " Error inspection VM: %s " ) % str ( e ) )
2019-06-17 04:12:39 +03:00
log . 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 :
2020-09-02 22:46:18 +03:00
return # pragma: no cover
2011-07-19 01:52:20 +04:00
2020-09-02 22:46:18 +03:00
if conn . is_remote ( ) : # pragma: no cover
2018-03-13 23:37:46 +03:00
return _inspection_error (
_ ( " Cannot inspect VM on remote connection " ) )
if conn . is_test ( ) :
2020-08-27 23:37:15 +03:00
return _make_fake_data ( vm )
2018-03-13 23:37:46 +03:00
2020-09-02 22:46:18 +03:00
return _perform_inspection ( conn , vm ) # pragma: no cover
2020-02-01 23:46:49 +03: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 19:35:26 +03:00
self . _q . put ( ( vm . conn . get_uri ( ) , vm . get_name ( ) ) )