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
2019-11-23 06:57:43 +03:00
from . import cloudinit
2019-06-08 00:32:51 +03:00
from . import unattended
2020-09-09 01:41:41 +03:00
from . import volumeupload
2018-10-13 01:35:09 +03:00
from . installertreemedia import InstallerTreeMedia
2019-06-10 01:48:22 +03:00
from . installerinject import perform_cdrom_injections
2019-06-17 04:34:47 +03:00
from . . domain import DomainOs
from . . devices import DeviceDisk
from . . osdict import OSDB
from . . logger import log
from . . import progress
2020-09-09 01:41:41 +03:00
from . . import xmlutil
2013-03-18 01:06:52 +04:00
2019-06-14 04:50:38 +03:00
def _make_testsuite_path ( path ) :
return os . path . join ( " /VIRTINST-TESTSUITE " ,
os . path . basename ( path ) . split ( " - " , 2 ) [ - 1 ] )
2013-07-17 15:53:47 +04:00
class Installer ( object ) :
2013-03-18 01:06:52 +04:00
"""
2019-01-31 01:15:52 +03:00
Class for kicking off VM installs . The VM is set up separately in a Guest
instance . This class tracks the install media / bootdev choice , alters the
Guest XML , boots it for the install , then saves the post install XML
config . The Guest is passed in via start_install , only install media
selection is done at __init__ time
: param cdrom : Path to a cdrom device or iso . Maps to virt - install - - cdrom
: param location : An install tree URI , local directory , or ISO / CDROM path .
Largely handled by installtreemedia helper class . Maps to virt - install
- - location
2019-02-01 02:07:09 +03:00
: param location_kernel : URL pointing to a kernel to fetch , or a relative
path to indicate where the kernel is stored in location
: param location_initrd : location_kernel , but pointing to an initrd
2019-06-12 00:34:18 +03:00
: param install_kernel : Kernel to install off of
2019-06-12 23:56:37 +03:00
: param install_initrd : Initrd to install off of
: param install_kernel_args : Kernel args < cmdline > to use . This overwrites
2019-06-13 00:55:30 +03:00
whatever the installer might request , unlike extra_args which will
2019-06-12 23:56:37 +03:00
append arguments .
2019-06-13 23:15:21 +03:00
: param no_install : If True , this installer specifically does not have
an install phase . We are just using it to create the initial XML .
2013-03-18 01:06:52 +04:00
"""
2019-02-01 02:07:09 +03:00
def __init__ ( self , conn , cdrom = None , location = None , install_bootdev = None ,
2019-06-12 00:34:18 +03:00
location_kernel = None , location_initrd = None ,
2019-06-13 23:15:21 +03:00
install_kernel = None , install_initrd = None , install_kernel_args = None ,
2020-02-03 15:23:05 +03:00
no_install = None , is_reinstall = False ) :
2013-07-17 15:53:47 +04:00
self . conn = conn
2014-01-12 00:28:21 +04:00
2018-10-13 16:42:15 +03:00
# Entry point for virt-manager 'Customize' wizard to change autostart
self . autostart = False
2019-05-23 22:55:58 +03:00
self . _install_cdrom_device_added = False
2019-06-07 13:00:56 +03:00
self . _unattended_install_cdrom_device = None
2019-06-08 22:20:16 +03:00
self . _tmpfiles = [ ]
2020-09-09 01:41:41 +03:00
self . _tmpvols = [ ]
2018-09-03 21:34:53 +03:00
self . _defaults_are_set = False
2019-03-29 00:44:47 +03:00
self . _unattended_data = None
2019-06-28 19:05:18 +03:00
self . _cloudinit_data = None
2013-03-18 01:06:52 +04:00
2019-06-13 23:15:21 +03:00
self . _install_bootdev = install_bootdev
self . _no_install = no_install
2020-02-03 15:23:05 +03:00
self . _is_reinstall = is_reinstall
self . _pre_reinstall_xml = None
2019-06-13 23:15:21 +03:00
2018-10-13 01:35:09 +03:00
self . _treemedia = None
2019-06-13 00:25:56 +03:00
self . _treemedia_bootconfig = None
2019-06-12 02:20:33 +03:00
self . _cdrom = None
2018-10-13 01:35:09 +03:00
if cdrom :
cdrom = InstallerTreeMedia . validate_path ( self . conn , cdrom )
self . _cdrom = cdrom
self . _install_bootdev = " cdrom "
2019-06-13 00:25:56 +03:00
elif ( location or location_kernel or location_initrd or
install_kernel or install_initrd ) :
2019-02-01 02:07:09 +03:00
self . _treemedia = InstallerTreeMedia ( self . conn , location ,
2019-06-13 00:25:56 +03:00
location_kernel , location_initrd ,
2019-06-13 00:55:30 +03:00
install_kernel , install_initrd , install_kernel_args )
2013-03-18 01:06:52 +04:00
2020-01-29 19:25:12 +03:00
##################
# Static helpers #
##################
@staticmethod
def cleanup_created_disks ( guest , meter ) :
"""
Remove any disks we created as part of the install . Only ever
called by clients .
"""
clean_disks = [ d for d in guest . devices . disk if d . storage_was_created ]
for disk in clean_disks :
2020-11-11 23:38:34 +03:00
path = disk . get_source_path ( )
2020-01-29 19:25:12 +03:00
log . debug ( " Removing created disk path= %s vol_object= %s " ,
2020-11-11 23:38:34 +03:00
path , disk . get_vol_object ( ) )
name = os . path . basename ( path )
2020-01-29 19:25:12 +03:00
try :
meter . start ( size = None , text = _ ( " Removing disk ' %s ' " ) % name )
if disk . get_vol_object ( ) :
disk . get_vol_object ( ) . delete ( )
else : # pragma: no cover
# This case technically shouldn't happen here, but
# it's here in case future assumptions change
2020-11-11 23:38:34 +03:00
os . unlink ( path )
2020-01-29 19:25:12 +03:00
meter . end ( 0 )
except Exception as e : # pragma: no cover
log . debug ( " Failed to remove disk ' %s ' " ,
name , exc_info = True )
log . error ( " Failed to remove disk ' %s ' : %s " , name , e )
2013-07-17 02:12:13 +04:00
###################
# Private helpers #
###################
2013-03-18 01:06:52 +04:00
2020-02-03 15:13:49 +03:00
def _make_cdrom_device ( self , path ) :
dev = DeviceDisk ( self . conn )
dev . device = dev . DEVICE_CDROM
2020-11-11 23:38:34 +03:00
dev . set_source_path ( path )
2020-02-03 15:13:49 +03:00
dev . sync_path_props ( )
dev . validate ( )
return dev
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 ) :
2019-05-23 22:55:58 +03:00
if self . _install_cdrom_device_added :
2019-06-12 00:19:01 +03:00
return # pragma: no cover
2018-10-13 01:35:09 +03:00
if not bool ( self . _cdrom_path ( ) ) :
2018-09-03 20:11:17 +03:00
return
2019-05-23 22:55:58 +03:00
self . _install_cdrom_device_added = True
2018-10-13 17:23:00 +03:00
2020-02-03 15:23:05 +03:00
if self . _is_reinstall :
cdroms = [ d for d in guest . devices . disk if d . is_cdrom ( ) ]
if cdroms :
dev = cdroms [ 0 ]
2020-11-11 23:38:34 +03:00
dev . set_source_path ( self . _cdrom_path ( ) )
2020-02-03 15:23:05 +03:00
return
dev = self . _make_cdrom_device ( self . _cdrom_path ( ) )
if self . _is_reinstall :
dev . set_defaults ( guest )
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 ( ) :
2019-05-23 22:55:58 +03:00
guest . devices . add_child ( dev , idx = idx )
2018-10-13 17:23:00 +03:00
return
2019-05-23 22:55:58 +03:00
guest . add_device ( dev )
2018-09-03 20:11:17 +03:00
def _remove_install_cdrom_media ( self , guest ) :
2019-05-23 22:55:58 +03:00
if not self . _install_cdrom_device_added :
2018-09-03 20:11:17 +03:00
return
if guest . osinfo . is_windows ( ) :
# Keep media attached for windows which has a multi stage install
return
2019-05-23 22:55:58 +03:00
for disk in guest . devices . disk :
2020-11-11 23:38:34 +03:00
if ( disk . is_cdrom ( ) and
disk . get_source_path ( ) == self . _cdrom_path ( ) ) :
disk . set_source_path ( None )
2019-05-23 22:55:58 +03:00
disk . sync_path_props ( )
break
2018-09-03 20:11:17 +03:00
2019-06-07 13:00:56 +03:00
def _add_unattended_install_cdrom_device ( self , guest , location ) :
if self . _unattended_install_cdrom_device :
2019-06-12 00:19:01 +03:00
return # pragma: no cover
2020-02-03 15:13:49 +03:00
dev = self . _make_cdrom_device ( location )
2019-03-29 00:44:45 +03:00
dev . set_defaults ( guest )
2019-06-07 13:00:56 +03:00
self . _unattended_install_cdrom_device = dev
guest . add_device ( self . _unattended_install_cdrom_device )
2019-03-29 00:44:45 +03:00
2019-06-08 22:33:37 +03:00
if self . conn . in_testsuite ( ) :
# Hack to set just the XML path differently for the test suite.
# Setting this via regular 'path' will error that it doesn't exist
2020-11-11 17:41:47 +03:00
dev . source . file = _make_testsuite_path ( location )
2019-06-08 22:33:37 +03:00
2019-06-07 13:00:56 +03:00
def _remove_unattended_install_cdrom_device ( self , guest ) :
2019-03-29 00:44:45 +03:00
dummy = guest
2019-06-07 13:00:56 +03:00
if not self . _unattended_install_cdrom_device :
2019-03-29 00:44:45 +03:00
return
2020-11-11 23:38:34 +03:00
self . _unattended_install_cdrom_device . set_source_path ( None )
2019-06-07 13:00:56 +03:00
self . _unattended_install_cdrom_device . sync_path_props ( )
2019-03-29 00:44:45 +03:00
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 ( ) ] ) )
2019-06-13 00:25:56 +03:00
def _alter_treemedia_bootconfig ( self , guest ) :
if not self . _treemedia :
return
2013-03-18 01:06:52 +04:00
2019-06-13 00:25:56 +03:00
kernel , initrd , kernel_args = self . _treemedia_bootconfig
if kernel :
2019-06-08 04:40:47 +03:00
guest . os . kernel = ( self . conn . in_testsuite ( ) and
2019-06-14 04:50:38 +03:00
_make_testsuite_path ( kernel ) or kernel )
2019-06-13 00:25:56 +03:00
if initrd :
2019-06-08 04:40:47 +03:00
guest . os . initrd = ( self . conn . in_testsuite ( ) and
2019-06-14 04:50:38 +03:00
_make_testsuite_path ( initrd ) or initrd )
2019-06-13 00:55:30 +03:00
if kernel_args :
guest . os . kernel_args = kernel_args
2013-03-18 01:06:52 +04:00
2019-06-13 00:25:56 +03:00
def _alter_bootconfig ( self , guest ) :
"""
Generate the portion of the guest xml that determines boot devices
and parameters . ( typically the < os > < / os > block )
: param guest : Guest instance we are installing
"""
guest . on_reboot = " destroy "
self . _alter_treemedia_bootconfig ( guest )
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 = [ ]
2019-03-22 18:23:41 +03:00
def _alter_install_resources ( self , guest , meter ) :
"""
Sets the appropriate amount of ram needed when performing a " network "
based installation
: param guest : Guest instance we are installing
"""
if not self . _treemedia :
return
if not self . _treemedia . requires_internet ( guest , meter ) :
return
2019-06-09 17:14:48 +03:00
ram = guest . osinfo . get_network_install_required_ram ( guest )
ram = ( ram or 0 ) / / 1024
if ram > guest . currentMemory :
2020-07-12 00:31:40 +03:00
msg = ( _ ( " Overriding memory to %(number)s MiB needed for "
" %(osname)s network install. " ) %
{ " number " : ram / / 1024 , " osname " : guest . osinfo . name } )
log . warning ( msg )
2019-06-09 17:14:48 +03:00
guest . currentMemory = ram
2019-05-11 20:34:21 +03:00
2013-07-17 02:12:13 +04:00
2020-09-09 01:41:41 +03:00
################
# Internal API #
################
def _should_upload_media ( self , guest ) :
"""
Return True if we should upload media to the connection scratchdir .
This doesn ' t consider if there is any media to upload, just whether
we _should_ upload if there _is_ media .
"""
scratchdir = InstallerTreeMedia . make_scratchdir ( guest )
system_scratchdir = InstallerTreeMedia . get_system_scratchdir ( guest )
if self . conn . is_remote ( ) :
return True
if self . conn . is_unprivileged ( ) :
return False
if scratchdir == system_scratchdir :
return False # pragma: no cover
return True
def _upload_media ( self , guest , meter , paths ) :
system_scratchdir = InstallerTreeMedia . get_system_scratchdir ( guest )
if ( not self . _should_upload_media ( guest ) and
not xmlutil . in_testsuite ( ) ) :
# We have access to system scratchdir, don't jump through hoops
log . debug ( " Have access to preferred scratchdir so "
" nothing to upload " ) # pragma: no cover
return paths # pragma: no cover
if not guest . conn . support_remote_url_install ( ) :
# Needed for the test_urls suite
log . debug ( " Media upload not supported " ) # pragma: no cover
return paths # pragma: no cover
newpaths , tmpvols = volumeupload . upload_paths (
guest . conn , system_scratchdir , meter , paths )
self . _tmpvols + = tmpvols
return newpaths
2013-07-17 02:12:13 +04:00
2019-07-30 19:31:23 +03:00
def _prepare_unattended_data ( self , guest , meter , scripts ) :
2020-09-09 01:41:41 +03:00
scratchdir = InstallerTreeMedia . make_scratchdir ( guest )
2019-07-30 18:23:35 +03:00
injections = [ ]
for script in scripts :
expected_filename = script . get_expected_filename ( )
unattended_cmdline = script . generate_cmdline ( )
log . debug ( " Generated unattended cmdline: %s " , unattended_cmdline )
scriptpath = script . write ( )
self . _tmpfiles . append ( scriptpath )
injections . append ( ( scriptpath , expected_filename ) )
2019-07-30 19:31:23 +03:00
drivers_location = guest . osinfo . get_pre_installable_drivers_location (
guest . os . arch )
2020-09-09 01:41:41 +03:00
drivers = unattended . download_drivers (
drivers_location , scratchdir , meter )
2019-10-02 18:27:19 +03:00
injections . extend ( drivers )
self . _tmpfiles . extend ( [ driverpair [ 0 ] for driverpair in drivers ] )
2019-07-30 19:31:23 +03:00
2020-09-09 01:41:41 +03:00
iso = perform_cdrom_injections ( injections , scratchdir )
2019-06-10 02:10:19 +03:00
self . _tmpfiles . append ( iso )
2020-09-09 01:41:41 +03:00
iso = self . _upload_media ( guest , meter , [ iso ] ) [ 0 ]
2019-06-08 21:16:52 +03:00
self . _add_unattended_install_cdrom_device ( guest , iso )
2019-07-30 18:23:35 +03:00
def _prepare_unattended_scripts ( self , guest , meter ) :
2019-06-10 21:20:45 +03:00
url = None
2019-07-16 18:14:30 +03:00
os_tree = None
2019-06-10 02:10:19 +03:00
if self . _treemedia :
2019-06-10 21:20:45 +03:00
if self . _treemedia . is_network_url ( ) :
url = self . location
2019-06-10 02:10:19 +03:00
os_media = self . _treemedia . get_os_media ( guest , meter )
2019-07-16 18:14:30 +03:00
os_tree = self . _treemedia . get_os_tree ( guest , meter )
2019-06-13 02:11:21 +03:00
injection_method = " initrd "
2019-06-10 02:10:19 +03:00
else :
2019-06-13 02:11:21 +03:00
if not guest . osinfo . is_windows ( ) :
2019-06-17 04:12:39 +03:00
log . warning ( " Attempting unattended method=cdrom injection "
2019-06-13 02:11:21 +03:00
" for a non-windows OS. If this doesn ' t work, try "
" passing install media to --location " )
2019-06-10 02:10:19 +03:00
osguess = OSDB . guess_os_by_iso ( self . cdrom )
os_media = osguess [ 1 ] if osguess else None
2019-06-13 02:11:21 +03:00
injection_method = " cdrom "
2019-06-10 02:10:19 +03:00
2019-07-30 18:23:35 +03:00
return unattended . prepare_install_scripts (
2019-07-16 18:14:30 +03:00
guest , self . _unattended_data , url ,
os_media , os_tree , injection_method )
2019-06-08 21:16:52 +03:00
2020-09-09 01:41:41 +03:00
def _prepare_treemedia ( self , guest , meter , unattended_scripts ) :
kernel , initrd , kernel_args = self . _treemedia . prepare ( guest , meter ,
unattended_scripts )
paths = [ kernel , initrd ]
kernel , initrd = self . _upload_media ( guest , meter , paths )
self . _treemedia_bootconfig = ( kernel , initrd , kernel_args )
def _prepare_cloudinit ( self , guest , meter ) :
scratchdir = InstallerTreeMedia . make_scratchdir ( guest )
filepairs = cloudinit . create_files ( scratchdir , self . _cloudinit_data )
for filepair in filepairs :
self . _tmpfiles . append ( filepair [ 0 ] )
iso = perform_cdrom_injections ( filepairs , scratchdir , cloudinit = True )
self . _tmpfiles . append ( iso )
iso = self . _upload_media ( guest , meter , [ iso ] ) [ 0 ]
self . _add_unattended_install_cdrom_device ( guest , iso )
2018-09-03 18:47:59 +03:00
def _prepare ( self , guest , meter ) :
2020-02-03 15:23:05 +03:00
if self . _is_reinstall :
self . _pre_reinstall_xml = guest . get_xml ( )
2019-07-30 18:23:35 +03:00
unattended_scripts = None
2019-06-10 02:10:19 +03:00
if self . _unattended_data :
2019-07-30 18:23:35 +03:00
unattended_scripts = self . _prepare_unattended_scripts ( guest , meter )
2019-06-10 02:10:19 +03:00
2018-10-13 01:35:09 +03:00
if self . _treemedia :
2020-09-09 01:41:41 +03:00
self . _prepare_treemedia ( guest , meter , unattended_scripts )
2019-06-10 02:10:19 +03:00
2019-07-30 18:23:35 +03:00
elif unattended_scripts :
2019-07-30 19:31:23 +03:00
self . _prepare_unattended_data ( guest , meter , unattended_scripts )
2018-09-03 18:47:59 +03:00
2019-06-28 19:05:18 +03:00
elif self . _cloudinit_data :
2020-09-09 01:41:41 +03:00
self . _prepare_cloudinit ( guest , meter )
2019-06-28 19:05:18 +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 )
2019-06-08 22:20:16 +03:00
2020-09-09 01:41:41 +03:00
for vol in self . _tmpvols :
log . debug ( " Removing volume ' %s ' " , vol . name ( ) )
vol . delete ( 0 )
self . _tmpvols = [ ]
2019-06-08 22:20:16 +03:00
for f in self . _tmpfiles :
2019-06-17 04:12:39 +03:00
log . debug ( " Removing %s " , str ( f ) )
2019-06-08 22:20:16 +03:00
os . unlink ( f )
2020-09-09 01:41:41 +03:00
self . _tmpfiles = [ ]
2018-09-03 20:11:17 +03:00
2018-09-03 18:47:59 +03:00
def _get_postinstall_bootdev ( self , guest ) :
2019-06-13 23:15:21 +03:00
if self . cdrom and self . _no_install :
2018-10-13 01:35:09 +03:00
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 ) :
2019-06-13 00:55:30 +03:00
if not self . _treemedia :
raise RuntimeError ( " Install method does not support "
" initrd injections. " )
self . _treemedia . set_initrd_injections ( initrd_injections )
def set_extra_args ( self , extra_args ) :
if not self . _treemedia :
raise RuntimeError ( " Kernel arguments are only supported with "
" location or kernel installs. " )
self . _treemedia . set_extra_args ( extra_args )
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
2020-02-03 15:23:05 +03:00
if self . _is_reinstall :
self . _pre_reinstall_xml = guest . get_xml ( )
2018-09-03 21:34:53 +03:00
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 )
2020-02-03 15:23:05 +03:00
if not self . _is_reinstall :
guest . set_defaults ( None )
2018-09-03 21:34:53 +03:00
self . _defaults_are_set = True
2018-09-03 18:47:59 +03:00
2019-02-07 21:58:55 +03:00
def get_search_paths ( self , guest ) :
2013-07-17 02:12:13 +04:00
"""
2019-02-07 21:58:55 +03:00
Return a list of paths that the hypervisor will need search access
for to perform this install .
2013-07-17 02:12:13 +04:00
"""
2019-02-07 21:58:55 +03:00
search_paths = [ ]
2020-09-09 01:41:41 +03:00
if ( ( self . _treemedia or
self . _cloudinit_data or
self . _unattended_data ) and
not self . _should_upload_media ( guest ) ) :
2019-06-07 20:58:19 +03:00
search_paths . append ( InstallerTreeMedia . make_scratchdir ( guest ) )
2019-02-07 21:58:55 +03:00
if self . _cdrom_path ( ) :
search_paths . append ( self . _cdrom_path ( ) )
return search_paths
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 .
"""
2020-11-10 18:35:14 +03:00
if self . has_cloudinit ( ) or self . has_unattended ( ) :
# These cases require post-boot altering to remove the
# temporary media. It isn't really an install phase and more
# like a 'final_xml' phase but we don't have a real abstraction
# for that yet
return True
2019-06-13 23:15:21 +03:00
if self . _no_install :
2018-10-13 01:35:09 +03:00
return False
return bool ( self . _cdrom or
self . _install_bootdev or
self . _treemedia )
2015-06-06 23:56:03 +03:00
2019-06-13 23:15:21 +03:00
def options_specified ( self ) :
"""
Return True if some explicit install option was actually passed in
Validate that some install option was actually passed in .
"""
if self . _no_install :
return True
return self . has_install_phase ( )
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
2019-01-31 01:07:36 +03:00
if self . _treemedia :
ret = self . _treemedia . detect_distro ( guest )
elif self . cdrom :
if guest . conn . is_remote ( ) :
2019-06-17 04:12:39 +03:00
log . debug ( " Can ' t detect distro for cdrom "
2019-01-31 01:07:36 +03:00
" remote connection. " )
2018-10-13 01:35:09 +03:00
else :
2019-01-31 01:07:36 +03:00
osguess = OSDB . guess_os_by_iso ( self . cdrom )
if osguess :
ret = osguess [ 0 ]
else :
2019-06-17 04:12:39 +03:00
log . debug ( " No media for distro detection. " )
2018-10-13 01:35:09 +03:00
2019-06-17 04:12:39 +03:00
log . debug ( " installer.detect_distro returned= %s " , ret )
2018-10-13 01:35:09 +03:00
return ret
2013-03-18 01:06:52 +04:00
2019-02-22 11:40:18 +03:00
def set_unattended_data ( self , unattended_data ) :
2019-06-10 02:10:19 +03:00
self . _unattended_data = unattended_data
2019-02-22 11:40:18 +03:00
2019-06-28 19:05:18 +03:00
def set_cloudinit_data ( self , cloudinit_data ) :
self . _cloudinit_data = cloudinit_data
2019-11-25 21:38:52 +03:00
def has_cloudinit ( self ) :
return bool ( self . _cloudinit_data )
2020-11-10 18:35:14 +03:00
def has_unattended ( self ) :
return bool ( self . _unattended_data )
2019-11-25 21:38:52 +03:00
2019-08-14 12:16:00 +03:00
def get_generated_password ( self ) :
if self . _cloudinit_data :
2019-11-21 01:42:14 +03:00
return self . _cloudinit_data . get_password_if_generated ( )
2019-08-14 12:16:00 +03: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 ,
2019-05-11 20:34:21 +03:00
guest . os . kernel_args , guest . on_reboot , guest . currentMemory ,
guest . memory )
2018-09-03 20:11:17 +03:00
return data
def _finish_get_install_xml ( self , guest , data ) :
( guest . os . bootorder , guest . os . kernel , guest . os . initrd ,
2019-05-11 20:34:21 +03:00
guest . os . kernel_args , guest . on_reboot , guest . currentMemory ,
guest . memory ) = data
2018-09-03 20:11:17 +03:00
2019-03-22 18:23:41 +03:00
def _get_install_xml ( self , guest , meter ) :
2018-09-03 20:11:17 +03:00
data = self . _prepare_get_install_xml ( guest )
try :
self . _alter_bootconfig ( guest )
2019-03-22 18:23:41 +03:00
self . _alter_install_resources ( guest , meter )
2018-09-03 20:11:17 +03:00
ret = guest . get_xml ( )
return ret
finally :
self . _remove_install_cdrom_media ( guest )
2019-06-07 13:00:56 +03:00
self . _remove_unattended_install_cdrom_device ( guest )
2018-09-03 20:11:17 +03:00
self . _finish_get_install_xml ( guest , data )
2019-03-22 18:23:41 +03:00
def _build_xml ( self , guest , meter ) :
2018-09-03 20:11:17 +03:00
install_xml = None
if self . has_install_phase ( ) :
2019-03-22 18:23:41 +03:00
install_xml = self . _get_install_xml ( guest , meter )
2020-02-03 15:23:05 +03:00
final_xml = self . _pre_reinstall_xml or guest . get_xml ( )
2018-09-03 20:11:17 +03:00
2019-06-17 04:12:39 +03:00
log . debug ( " Generated install XML: %s " ,
2018-09-03 20:11:17 +03:00
( install_xml and ( " \n " + install_xml ) or " None required " ) )
2019-06-17 04:12:39 +03:00
log . debug ( " Generated boot XML: \n %s " , final_xml )
2018-09-03 20:11:17 +03:00
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 ( )
2019-06-10 21:15:50 +03:00
except Exception : # pragma: no cover
2018-09-03 20:11:17 +03:00
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... " )
2019-06-08 00:32:51 +03:00
meter = progress . ensure_meter ( meter )
2018-09-03 20:11:17 +03:00
meter . start ( size = None , text = meter_label )
needs_boot = doboot or self . has_install_phase ( )
2020-02-03 15:23:05 +03:00
if guest . type == " vz " and not self . _is_reinstall :
2018-09-03 20:11:17 +03:00
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 :
2019-06-17 04:12:39 +03:00
log . debug ( " XML fetched from libvirt object: \n %s " ,
2018-09-03 20:41:39 +03:00
domain . XMLDesc ( 0 ) )
2019-06-10 21:15:50 +03:00
except Exception as e : # pragma: no cover
2019-06-17 04:12:39 +03:00
log . 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 )
2019-06-10 21:15:50 +03:00
except Exception as e : # pragma: no cover
2019-06-07 23:48:21 +03:00
if not self . conn . support . is_error_nosupport ( e ) :
raise
2019-06-17 04:12:39 +03:00
log . warning ( " Could not set autostart flag: libvirt "
2019-06-07 23:48:21 +03:00
" connection does not support autostart. " )
2018-09-03 20:11:17 +03:00
######################
# 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
"""
2020-02-03 15:23:05 +03:00
if not self . _is_reinstall and not return_xml :
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 . _prepare ( guest , meter )
2020-02-03 15:23:05 +03:00
if not dry and not self . _is_reinstall :
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
2019-03-22 18:23:41 +03:00
install_xml , final_xml = self . _build_xml ( guest , meter )
2019-06-12 00:19:01 +03:00
if dry or return_xml :
2018-09-03 20:11:17 +03:00
return ( install_xml , final_xml )
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 )