2013-03-18 01:06:52 +04:00
#
# Common code for all guests
#
2013-10-28 00:59:46 +04:00
# Copyright 2006-2009, 2013 Red Hat, Inc.
2013-03-18 01:06:52 +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.
2013-03-18 01:06:52 +04:00
import os
import logging
2018-09-03 20:11:17 +03:00
import libvirt
2018-03-20 19:27:37 +03:00
from . devices import DeviceDisk
2018-03-20 22:10:04 +03:00
from . domain import DomainOs
2018-10-13 01:35:09 +03:00
from . installertreemedia import InstallerTreeMedia
2018-09-03 20:11:17 +03:00
from . import util
2013-03-18 01:06:52 +04:00
2013-07-17 15:53:47 +04:00
class Installer ( object ) :
2013-03-18 01:06:52 +04:00
"""
Installer classes attempt to encapsulate all the parameters needed
to ' install ' a guest : essentially , booting the guest with the correct
media for the OS install phase ( if there is one ) , and setting up the
guest to boot to the correct media for all subsequent runs .
Some of the actual functionality :
- Determining what type of install media has been requested , and
representing it correctly to the Guest
- Fetching install kernel / initrd or boot . iso from a URL
- Setting the boot device as appropriate depending on whether we
are booting into an OS install , or booting post - install
Some of the information that the Installer needs to know to accomplish
this :
- Install media location ( could be a URL , local path , . . . )
- Virtualization type ( parameter ' os_type ' ) ( ' xen ' , ' hvm ' , etc . )
- Hypervisor name ( parameter ' type ' ) ( ' qemu ' , ' kvm ' , ' xen ' , etc . )
- Guest architecture ( ' i686 ' , ' x86_64 ' )
"""
2018-10-13 01:35:09 +03:00
def __init__ ( self , conn , cdrom = None , location = None , install_bootdev = None ) :
2013-07-17 15:53:47 +04:00
self . conn = conn
2014-01-12 00:28:21 +04:00
2014-09-11 05:33:50 +04:00
self . livecd = False
2018-10-13 23:29:32 +03:00
self . extra_args = [ ]
2013-07-17 04:05:24 +04:00
2018-10-13 16:42:15 +03:00
# Entry point for virt-manager 'Customize' wizard to change autostart
self . autostart = False
2018-10-13 01:35:09 +03:00
self . _install_bootdev = install_bootdev
2013-07-17 04:05:24 +04:00
self . _install_kernel = None
self . _install_initrd = None
2018-09-03 20:11:17 +03:00
self . _install_cdrom_device = None
2018-09-03 21:34:53 +03:00
self . _defaults_are_set = False
2013-03-18 01:06:52 +04:00
2018-10-13 01:35:09 +03:00
self . _cdrom = None
self . _treemedia = None
if cdrom :
cdrom = InstallerTreeMedia . validate_path ( self . conn , cdrom )
self . _cdrom = cdrom
self . _install_bootdev = " cdrom "
if location :
self . _treemedia = InstallerTreeMedia ( self . conn , location )
2013-03-18 01:06:52 +04:00
2013-07-17 02:12:13 +04:00
###################
# Private helpers #
###################
2013-03-18 01:06:52 +04:00
2018-10-13 01:35:09 +03:00
def _cdrom_path ( self ) :
if self . _treemedia :
return self . _treemedia . cdrom_path ( )
return self . _cdrom
2018-09-03 20:11:17 +03:00
def _add_install_cdrom_device ( self , guest ) :
if self . _install_cdrom_device :
return
2018-10-13 01:35:09 +03:00
if not bool ( self . _cdrom_path ( ) ) :
2018-09-03 20:11:17 +03:00
return
dev = DeviceDisk ( self . conn )
dev . device = dev . DEVICE_CDROM
2018-10-13 17:11:26 +03:00
dev . path = self . _cdrom_path ( )
dev . sync_path_props ( )
dev . validate ( )
2018-09-03 20:11:17 +03:00
self . _install_cdrom_device = dev
2018-10-13 17:23:00 +03:00
# Insert the CDROM before any other CDROM, so boot=cdrom picks
# it as the priority
for idx , disk in enumerate ( guest . devices . disk ) :
if disk . is_cdrom ( ) :
guest . devices . add_child ( self . _install_cdrom_device , idx = idx )
return
guest . add_device ( self . _install_cdrom_device )
2018-09-03 20:11:17 +03:00
def _remove_install_cdrom_media ( self , guest ) :
if not self . _install_cdrom_device :
return
if self . livecd :
return
if guest . osinfo . is_windows ( ) :
# Keep media attached for windows which has a multi stage install
return
self . _install_cdrom_device . path = None
self . _install_cdrom_device . sync_path_props ( )
2018-09-03 18:47:59 +03:00
def _build_boot_order ( self , guest , bootdev ) :
bootorder = [ bootdev ]
2013-03-18 01:06:52 +04:00
# If guest has an attached disk, always have 'hd' in the boot
# list, so disks are marked as bootable/installable (needed for
# windows virtio installs, and booting local disk from PXE)
2018-03-21 00:23:34 +03:00
for disk in guest . devices . disk :
2013-03-18 01:06:52 +04:00
if disk . device == disk . DEVICE_DISK :
2013-07-17 15:53:47 +04:00
bootdev = " hd "
2013-03-18 01:06:52 +04:00
if bootdev not in bootorder :
bootorder . append ( bootdev )
break
return bootorder
2018-09-03 21:34:53 +03:00
def _can_set_guest_bootorder ( self , guest ) :
return ( not guest . os . is_container ( ) and
not guest . os . kernel and
not any ( [ d . boot . order for d in guest . devices . get_all ( ) ] ) )
2018-09-03 20:11:17 +03:00
def _alter_bootconfig ( self , guest ) :
2013-03-18 01:06:52 +04:00
"""
Generate the portion of the guest xml that determines boot devices
and parameters . ( typically the < os > < / os > block )
2018-02-14 15:17:31 +03:00
: param guest : Guest instance we are installing
2013-03-18 01:06:52 +04:00
"""
2018-09-03 18:47:59 +03:00
guest . on_reboot = " destroy "
2013-07-17 04:05:24 +04:00
2013-07-17 15:53:47 +04:00
if self . _install_kernel :
2015-04-07 23:38:52 +03:00
guest . os . kernel = self . _install_kernel
2013-07-17 15:53:47 +04:00
if self . _install_initrd :
2015-04-07 23:38:52 +03:00
guest . os . initrd = self . _install_initrd
2018-10-13 23:29:32 +03:00
if self . extra_args :
guest . os . kernel_args = " " . join ( self . extra_args )
2013-03-18 01:06:52 +04:00
2018-10-13 01:35:09 +03:00
bootdev = self . _install_bootdev
2018-09-03 21:34:53 +03:00
if bootdev and self . _can_set_guest_bootorder ( guest ) :
2018-09-03 18:47:59 +03:00
guest . os . bootorder = self . _build_boot_order ( guest , bootdev )
else :
guest . os . bootorder = [ ]
2013-07-17 02:12:13 +04:00
##########################
# Internal API overrides #
##########################
2018-09-03 18:47:59 +03:00
def _prepare ( self , guest , meter ) :
2018-10-13 01:35:09 +03:00
if self . _treemedia :
k , i , a = self . _treemedia . prepare ( guest , meter )
self . _install_kernel = k
self . _install_initrd = i
2018-10-13 23:30:19 +03:00
if a and " VIRTINST_INITRD_TEST " not in os . environ :
2018-10-13 23:29:32 +03:00
self . extra_args . append ( a )
2018-09-03 18:47:59 +03:00
2018-09-03 20:11:17 +03:00
def _cleanup ( self , guest ) :
2018-10-13 01:35:09 +03:00
if self . _treemedia :
self . _treemedia . cleanup ( guest )
2018-09-03 20:11:17 +03:00
2018-09-03 18:47:59 +03:00
def _get_postinstall_bootdev ( self , guest ) :
2018-10-13 01:35:09 +03:00
if self . cdrom and self . livecd :
return DomainOs . BOOT_DEVICE_CDROM
if self . _install_bootdev :
2018-10-12 22:15:20 +03:00
if any ( [ d for d in guest . devices . disk
if d . device == d . DEVICE_DISK ] ) :
return DomainOs . BOOT_DEVICE_HARDDISK
2018-10-13 01:35:09 +03:00
return self . _install_bootdev
2018-10-12 22:15:20 +03:00
2018-09-03 17:58:25 +03:00
device = guest . devices . disk and guest . devices . disk [ 0 ] . device or None
if device == DeviceDisk . DEVICE_DISK :
return DomainOs . BOOT_DEVICE_HARDDISK
elif device == DeviceDisk . DEVICE_CDROM :
return DomainOs . BOOT_DEVICE_CDROM
elif device == DeviceDisk . DEVICE_FLOPPY :
return DomainOs . BOOT_DEVICE_FLOPPY
return DomainOs . BOOT_DEVICE_HARDDISK
2013-07-17 02:12:13 +04:00
##############
# Public API #
##############
2018-10-13 01:35:09 +03:00
@property
def location ( self ) :
if self . _treemedia :
return self . _treemedia . location
@property
def cdrom ( self ) :
return self . _cdrom
def set_initrd_injections ( self , initrd_injections ) :
if self . _treemedia :
2018-10-13 22:51:38 +03:00
self . _treemedia . initrd_injections = initrd_injections
2018-10-13 01:35:09 +03:00
2018-09-03 21:34:53 +03:00
def set_install_defaults ( self , guest ) :
2018-09-03 18:47:59 +03:00
"""
2018-09-03 21:34:53 +03:00
Allow API users to set defaults ahead of time if they want it .
Used by vmmDomainVirtinst so the ' Customize before install ' dialog
shows accurate values .
If the user doesn ' t explicitly call this, it will be called by
start_install ( )
2018-09-03 18:47:59 +03:00
"""
2018-09-03 21:34:53 +03:00
if self . _defaults_are_set :
return
self . _add_install_cdrom_device ( guest )
if not guest . os . bootorder and self . _can_set_guest_bootorder ( guest ) :
bootdev = self . _get_postinstall_bootdev ( guest )
guest . os . bootorder = self . _build_boot_order ( guest , bootdev )
guest . set_defaults ( None )
self . _defaults_are_set = True
2018-09-03 18:47:59 +03:00
2013-07-17 02:12:13 +04:00
def scratchdir_required ( self ) :
"""
Returns true if scratchdir is needed for the passed install parameters .
Apps can use this to determine if they should attempt to ensure
scratchdir permissions are adequate
"""
2018-10-13 01:35:09 +03:00
return bool ( self . _treemedia )
2013-07-17 02:12:13 +04:00
2013-03-18 01:06:52 +04:00
def has_install_phase ( self ) :
"""
Return True if the requested setup is actually installing an OS
into the guest . Things like LiveCDs , Import , or a manually specified
bootorder do not have an install phase .
"""
2018-10-13 01:35:09 +03:00
if self . cdrom and self . livecd :
return False
return bool ( self . _cdrom or
self . _install_bootdev or
self . _treemedia )
2015-06-06 23:56:03 +03:00
2013-09-26 21:04:28 +04:00
def check_location ( self , guest ) :
2013-03-18 01:06:52 +04:00
"""
2013-07-17 02:12:13 +04:00
Validate self . location seems to work . This will might hit the
network so we don ' t want to do it on demand.
2013-03-18 01:06:52 +04:00
"""
2018-10-13 01:35:09 +03:00
if self . _treemedia :
return self . _treemedia . check_location ( guest )
2013-07-17 02:12:13 +04:00
return True
2013-03-18 01:06:52 +04:00
2013-09-26 21:04:28 +04:00
def detect_distro ( self , guest ) :
2013-03-18 01:06:52 +04:00
"""
Attempt to detect the distro for the Installer ' s ' location ' . If
an error is encountered in the detection process ( or if detection
2014-09-07 19:57:04 +04:00
is not relevant for the Installer type ) , None is returned .
2013-03-18 01:06:52 +04:00
2018-02-14 15:17:31 +03:00
: returns : distro variant string , or None
2013-03-18 01:06:52 +04:00
"""
2018-10-13 01:35:09 +03:00
ret = None
try :
if self . _treemedia :
ret = self . _treemedia . detect_distro ( guest )
elif self . cdrom :
ret = InstallerTreeMedia . detect_iso_distro ( guest , self . cdrom )
else :
logging . debug ( " No media for distro detection. " )
except Exception :
logging . debug ( " Error attempting to detect distro. " , exc_info = True )
logging . debug ( " installer.detect_distro returned= %s " , ret )
return ret
2013-03-18 01:06:52 +04:00
2013-04-12 17:51:26 +04:00
2018-09-03 20:11:17 +03:00
##########################
# guest install handling #
##########################
def _prepare_get_install_xml ( self , guest ) :
# We do a shallow copy of the OS block here, so that we can
# set the install time properties but not permanently overwrite
# any config the user explicitly requested.
data = ( guest . os . bootorder , guest . os . kernel , guest . os . initrd ,
guest . os . kernel_args , guest . on_reboot )
return data
def _finish_get_install_xml ( self , guest , data ) :
( guest . os . bootorder , guest . os . kernel , guest . os . initrd ,
guest . os . kernel_args , guest . on_reboot ) = data
def _get_install_xml ( self , guest ) :
data = self . _prepare_get_install_xml ( guest )
try :
self . _alter_bootconfig ( guest )
ret = guest . get_xml ( )
return ret
finally :
self . _remove_install_cdrom_media ( guest )
self . _finish_get_install_xml ( guest , data )
def _build_xml ( self , guest ) :
install_xml = None
if self . has_install_phase ( ) :
install_xml = self . _get_install_xml ( guest )
final_xml = guest . get_xml ( )
logging . debug ( " Generated install XML: %s " ,
( install_xml and ( " \n " + install_xml ) or " None required " ) )
logging . debug ( " Generated boot XML: \n %s " , final_xml )
return install_xml , final_xml
def _manual_transient_create ( self , install_xml , final_xml , needs_boot ) :
"""
For hypervisors ( like vz ) that don ' t implement createXML,
we need to define + start , and undefine on start failure
"""
domain = self . conn . defineXML ( install_xml or final_xml )
if not needs_boot :
return domain
# Handle undefining the VM if the initial startup fails
try :
domain . create ( )
except Exception :
try :
domain . undefine ( )
except Exception :
pass
raise
if install_xml and install_xml != final_xml :
domain = self . conn . defineXML ( final_xml )
return domain
def _create_guest ( self , guest ,
meter , install_xml , final_xml , doboot , transient ) :
"""
Actually do the XML logging , guest defining / creating
: param doboot : Boot guest even if it has no install phase
"""
meter_label = _ ( " Creating domain... " )
meter = util . ensure_meter ( meter )
meter . start ( size = None , text = meter_label )
needs_boot = doboot or self . has_install_phase ( )
if guest . type == " vz " :
if transient :
raise RuntimeError ( _ ( " Domain type ' vz ' doesn ' t support "
" transient installs. " ) )
domain = self . _manual_transient_create (
install_xml , final_xml , needs_boot )
else :
if transient or needs_boot :
domain = self . conn . createXML ( install_xml or final_xml , 0 )
if not transient :
domain = self . conn . defineXML ( final_xml )
try :
logging . debug ( " XML fetched from libvirt object: \n %s " ,
2018-09-03 20:41:39 +03:00
domain . XMLDesc ( 0 ) )
2018-09-03 20:11:17 +03:00
except Exception as e :
logging . debug ( " Error fetching XML from libvirt object: %s " , e )
2018-09-03 20:41:39 +03:00
return domain
2018-09-03 20:11:17 +03:00
2018-09-03 20:41:39 +03:00
def _flag_autostart ( self , domain ) :
2018-09-03 20:11:17 +03:00
"""
2018-09-03 20:41:39 +03:00
Set the autostart flag for domain if the user requested it
2018-09-03 20:11:17 +03:00
"""
try :
2018-09-03 20:41:39 +03:00
domain . setAutostart ( True )
2018-09-03 20:11:17 +03:00
except libvirt . libvirtError as e :
if util . is_error_nosupport ( e ) :
logging . warning ( " Could not set autostart flag: libvirt "
" connection does not support autostart. " )
else :
raise e
######################
# Public install API #
######################
def start_install ( self , guest , meter = None ,
dry = False , return_xml = False ,
2018-10-13 16:42:15 +03:00
doboot = True , transient = False ) :
2018-09-03 20:11:17 +03:00
"""
2018-09-03 20:45:09 +03:00
Begin the guest install . Will add install media to the guest config ,
launch it , then redefine the XML with the postinstall config .
2018-09-03 20:11:17 +03:00
: param return_xml : Don ' t create the guest, just return generated XML
"""
2018-09-03 22:45:26 +03:00
guest . validate_name ( guest . conn , guest . name )
2018-09-03 21:34:53 +03:00
self . set_install_defaults ( guest )
2018-09-03 20:11:17 +03:00
try :
self . _cleanup ( guest )
self . _prepare ( guest , meter )
if not dry :
2018-09-03 23:44:38 +03:00
for dev in guest . devices . disk :
dev . build_storage ( meter )
2018-09-03 20:11:17 +03:00
install_xml , final_xml = self . _build_xml ( guest )
if return_xml :
return ( install_xml , final_xml )
if dry :
return
2018-09-03 20:41:39 +03:00
domain = self . _create_guest (
guest , meter , install_xml , final_xml ,
doboot , transient )
2018-09-03 20:11:17 +03:00
2018-10-13 16:42:15 +03:00
if self . autostart :
2018-09-03 20:45:09 +03:00
self . _flag_autostart ( domain )
2018-09-03 20:41:39 +03:00
return domain
2018-09-03 20:11:17 +03:00
finally :
self . _cleanup ( guest )
def get_created_disks ( self , guest ) :
return [ d for d in guest . devices . disk if d . storage_was_created ]
def cleanup_created_disks ( self , guest , meter ) :
"""
Remove any disks we created as part of the install . Only ever
called by clients .
"""
clean_disks = self . get_created_disks ( guest )
if not clean_disks :
return
for disk in clean_disks :
logging . debug ( " Removing created disk path= %s vol_object= %s " ,
disk . path , disk . get_vol_object ( ) )
name = os . path . basename ( disk . path )
try :
meter . start ( size = None , text = _ ( " Removing disk ' %s ' " ) % name )
if disk . get_vol_object ( ) :
disk . get_vol_object ( ) . delete ( )
else :
os . unlink ( disk . path )
meter . end ( 0 )
except Exception as e :
logging . debug ( " Failed to remove disk ' %s ' " ,
name , exc_info = True )
logging . error ( " Failed to remove disk ' %s ' : %s " , name , e )