2020-01-27 01:12:09 +03:00
# Copyright 2005-2014 Red Hat, Inc.
#
# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
import argparse
import atexit
2020-09-11 02:45:37 +03:00
import os
2020-01-27 01:12:09 +03:00
import sys
import time
import select
import libvirt
import virtinst
from . import cli
2020-09-17 09:44:02 +03:00
from . cli import fail , fail_conflicting , print_stdout , print_stderr
2020-01-27 01:12:09 +03:00
from . import Network
from . guest import Guest
from . logger import log
##############################
# Validation utility helpers #
##############################
INSTALL_METHODS = " --location URL, --cdrom CD/ISO, --pxe, --import, --boot hd|cdrom|... "
def supports_pxe ( guest ) :
"""
Return False if we are pretty sure the config doesn ' t support PXE
"""
for nic in guest . devices . interface :
if nic . type == nic . TYPE_USER :
continue
if nic . type != nic . TYPE_VIRTUAL :
return True
try :
netobj = nic . conn . networkLookupByName ( nic . source )
xmlobj = Network ( nic . conn , parsexml = netobj . XMLDesc ( 0 ) )
return xmlobj . can_pxe ( )
except Exception : # pragma: no cover
log . debug ( " Error checking if PXE supported " , exc_info = True )
return True
return False
def check_cdrom_option_error ( options ) :
if options . cdrom_short and options . cdrom :
2020-09-17 09:44:02 +03:00
fail_conflicting ( " -c " , " --cdrom " )
2020-01-27 01:12:09 +03:00
if options . cdrom_short :
if " :// " in options . cdrom_short :
2020-09-17 09:44:03 +03:00
fail ( _ ( " -c specified with what looks like a libvirt URI. "
" Did you mean to use --connect? If not, use --cdrom "
" instead " ) )
2020-01-27 01:12:09 +03:00
options . cdrom = options . cdrom_short
#################################
# Back compat option conversion #
#################################
def convert_old_printxml ( options ) :
if options . xmlstep :
options . xmlonly = options . xmlstep
del ( options . xmlstep )
def convert_old_sound ( options ) :
if not options . sound :
return
2021-10-04 22:38:24 +03:00
for idx , dummy in enumerate ( options . sound ) :
2020-01-27 01:12:09 +03:00
if options . sound [ idx ] is None :
options . sound [ idx ] = " default "
def convert_old_init ( options ) :
if not options . init :
return
if not options . boot :
options . boot = [ " " ]
options . boot [ - 1 ] + = " ,init= %s " % options . init
log . debug ( " Converted old --init to --boot %s " , options . boot [ - 1 ] )
def _do_convert_old_disks ( options ) :
paths = virtinst . xmlutil . listify ( options . file_paths )
sizes = virtinst . xmlutil . listify ( options . disksize )
def padlist ( l , padsize ) :
l = virtinst . xmlutil . listify ( l )
l . extend ( ( padsize - len ( l ) ) * [ None ] )
return l
disklist = padlist ( paths , max ( 0 , len ( sizes ) ) )
sizelist = padlist ( sizes , len ( disklist ) )
opts = [ ]
for idx , path in enumerate ( disklist ) :
optstr = " "
if path :
optstr + = " path= %s " % path
if sizelist [ idx ] :
if optstr :
optstr + = " , "
optstr + = " size= %s " % sizelist [ idx ]
if options . sparse is False :
if optstr :
optstr + = " , "
optstr + = " sparse=no "
log . debug ( " Converted to new style: --disk %s " , optstr )
opts . append ( optstr )
options . disk = opts
def convert_old_disks ( options ) :
if options . nodisks and ( options . file_paths or
options . disk or
options . disksize ) :
fail ( _ ( " Cannot specify storage and use --nodisks " ) )
if ( ( options . file_paths or options . disksize or not options . sparse ) and
options . disk ) :
fail ( _ ( " Cannot mix --file, --nonsparse, or --file-size with --disk "
" options. Use --disk PATH[,size=SIZE][,sparse=yes|no] " ) )
if not options . disk :
if options . nodisks :
options . disk = [ " none " ]
else :
_do_convert_old_disks ( options )
del ( options . file_paths )
del ( options . disksize )
del ( options . sparse )
del ( options . nodisks )
log . debug ( " Distilled --disk options: %s " , options . disk )
def convert_old_os_options ( options ) :
if not options . os_variant and options . old_os_type :
options . os_variant = options . old_os_type
del ( options . old_os_type )
def convert_old_memory ( options ) :
if options . memory :
return
if not options . oldmemory :
return
options . memory = str ( options . oldmemory )
def convert_old_cpuset ( options ) :
if not options . cpuset :
return
newvcpus = options . vcpus or [ ]
newvcpus . append ( " ,cpuset= %s " % options . cpuset )
options . vcpus = newvcpus
log . debug ( " Generated compat cpuset: --vcpus %s " , options . vcpus [ - 1 ] )
def convert_old_networks ( options ) :
if options . nonetworks :
options . network = [ " none " ]
macs = virtinst . xmlutil . listify ( options . mac )
networks = virtinst . xmlutil . listify ( options . network )
bridges = virtinst . xmlutil . listify ( options . bridge )
if bridges and networks :
2020-09-17 09:44:02 +03:00
fail_conflicting ( " --bridge " , " --network " )
2020-01-27 01:12:09 +03:00
if bridges :
# Convert old --bridges to --networks
networks = [ " bridge: " + b for b in bridges ]
def padlist ( l , padsize ) :
l = virtinst . xmlutil . listify ( l )
l . extend ( ( padsize - len ( l ) ) * [ None ] )
return l
# If a plain mac is specified, have it imply a default network
networks = padlist ( networks , max ( len ( macs ) , 1 ) )
macs = padlist ( macs , len ( networks ) )
for idx , ignore in enumerate ( networks ) :
if networks [ idx ] is None :
networks [ idx ] = " default "
if macs [ idx ] :
networks [ idx ] + = " ,mac= %s " % macs [ idx ]
# Handle old format of bridge:foo instead of bridge=foo
for prefix in [ " network " , " bridge " ] :
if networks [ idx ] . startswith ( prefix + " : " ) :
networks [ idx ] = networks [ idx ] . replace ( prefix + " : " ,
prefix + " = " )
del ( options . mac )
del ( options . bridge )
del ( options . nonetworks )
options . network = networks
log . debug ( " Distilled --network options: %s " , options . network )
def convert_old_graphics ( options ) :
vnc = options . vnc
vncport = options . vncport
vnclisten = options . vnclisten
nographics = options . nographics
sdl = options . sdl
keymap = options . keymap
graphics = options . graphics
if graphics and ( vnc or sdl or keymap or vncport or vnclisten ) :
fail ( _ ( " Cannot mix --graphics and old style graphical options " ) )
optnum = sum ( [ bool ( g ) for g in [ vnc , nographics , sdl , graphics ] ] )
if optnum > 1 :
raise ValueError ( _ ( " Can ' t specify more than one of VNC, SDL, "
" --graphics or --nographics " ) )
if options . graphics :
return
if optnum == 0 :
return
# Build a --graphics command line from old style opts
optstr = ( ( vnc and " vnc " ) or
( sdl and " sdl " ) or
( nographics and ( " none " ) ) )
if vnclisten :
optstr + = " ,listen= %s " % vnclisten
if vncport :
optstr + = " ,port= %s " % vncport
if keymap :
optstr + = " ,keymap= %s " % keymap
log . debug ( " --graphics compat generated: %s " , optstr )
options . graphics = [ optstr ]
def convert_old_features ( options ) :
if options . features :
return
opts = [ ]
if options . noacpi :
opts . append ( " acpi=off " )
if options . noapic :
opts . append ( " apic=off " )
if opts :
options . features = [ " , " . join ( opts ) ]
def convert_wait_zero ( options ) :
# Historical back compat, --wait 0 is identical to --noautoconsole
if options . wait == 0 :
log . warning ( " Treating --wait 0 as --noautoconsole " )
options . autoconsole = " none "
options . wait = None
##################################
# Install media setup/validation #
##################################
def do_test_media_detection ( conn , options ) :
url = options . test_media_detection
guest = virtinst . Guest ( conn )
if options . arch :
guest . os . arch = options . arch
if options . os_type :
guest . os . os_type = options . os_type
guest . set_capabilities_defaults ( )
installer = virtinst . Installer ( conn , location = url )
print_stdout ( installer . detect_distro ( guest ) , do_force = True )
#############################
# General option validation #
#############################
def storage_specified ( options , guest ) :
if guest . os . is_container ( ) :
return True
return options . disk or options . filesystem
def memory_specified ( guest ) :
return guest . memory or guest . currentMemory or guest . cpu . cells
def validate_required_options ( options , guest , installer ) :
# Required config. Don't error right away if nothing is specified,
# aggregate the errors to help first time users get it right
msg = " "
2020-02-03 15:23:05 +03:00
if not options . reinstall :
if not memory_specified ( guest ) :
msg + = " \n " + _ ( " --memory amount in MiB is required " )
2020-01-27 01:12:09 +03:00
2020-02-03 15:23:05 +03:00
if not storage_specified ( options , guest ) :
msg + = " \n " + (
_ ( " --disk storage must be specified (override with --disk none) " ) )
2020-01-27 01:12:09 +03:00
if not guest . os . is_container ( ) and not installer . options_specified ( ) :
msg + = " \n " + (
_ ( " An install method must be specified \n ( %(methods)s ) " ) %
{ " methods " : INSTALL_METHODS } )
if msg :
fail ( msg )
def show_console_warnings ( installer , autoconsole ) :
if not installer . cdrom :
return
if not autoconsole . is_text ( ) :
return
log . warning ( _ ( " CDROM media does not print to the text console "
" by default, so you likely will not see text install output. "
" You might want to use --location. " ) + " " +
_ ( " See the man page for examples of "
" using --location with CDROM media " ) )
def _show_memory_warnings ( guest ) :
if not guest . currentMemory :
return
res = guest . osinfo . get_recommended_resources ( )
rammb = guest . currentMemory / / 1024
minram = ( res . get_minimum_ram ( guest . os . arch ) or 0 )
if minram :
if ( minram / / 1024 ) > guest . currentMemory :
2020-07-12 00:31:40 +03:00
log . warning ( _ ( " Requested memory %(mem1)s MiB is less than the "
" recommended %(mem2)s MiB for OS %(osname)s " ) ,
{ " mem1 " : rammb , " mem2 " : minram / / ( 1024 * 1024 ) ,
" osname " : guest . osinfo . name } )
2020-01-27 01:12:09 +03:00
elif rammb < 17 :
log . warning ( _ ( " Requested memory %s MiB is abnormally low. "
" Were you trying to specify GiB? " ) , rammb )
def show_guest_warnings ( options , guest , osdata ) :
if options . pxe and not supports_pxe ( guest ) :
log . warning (
_ ( " The guest ' s network configuration may not support PXE " ) )
# Limit it to hvm x86 guests which presently our defaults
# only really matter for
if ( guest . osinfo . name == " generic " and
2020-09-15 02:05:58 +03:00
not osdata . is_generic_requested ( ) and
2020-01-27 01:12:09 +03:00
guest . os . is_x86 ( ) and guest . os . is_hvm ( ) ) :
log . warning ( _ ( " No operating system detected, VM performance may "
" suffer. Specify an OS with --os-variant for optimal results. " ) )
_show_memory_warnings ( guest )
##########################
# Guest building helpers #
##########################
def get_location_for_os ( guest , osname , profile = None ) :
osinfo = virtinst . OSDB . lookup_os ( osname , raise_error = True )
location = osinfo . get_location ( guest . os . arch , profile )
print_stdout ( _ ( " Using {osname} --location {url} " ) . format (
osname = osname , url = location ) )
return location
def build_installer ( options , guest , installdata ) :
cdrom = None
location = None
location_kernel = None
location_initrd = None
2020-02-03 15:23:05 +03:00
is_reinstall = bool ( options . reinstall )
2020-01-27 01:12:09 +03:00
unattended_data = None
extra_args = options . extra_args
install_bootdev = installdata . bootdev
install_kernel = installdata . kernel
install_initrd = installdata . initrd
install_kernel_args = installdata . kernel_args
install_os = installdata . os
no_install = installdata . no_install
if installdata . kernel_args :
if installdata . kernel_args_overwrite :
install_kernel_args = installdata . kernel_args
else :
extra_args = [ installdata . kernel_args ]
if options . unattended :
unattended_data = cli . parse_unattended ( options . unattended )
if install_os :
profile = unattended_data . profile if unattended_data else None
location = get_location_for_os ( guest , install_os , profile )
elif options . location :
( location ,
location_kernel ,
location_initrd ) = cli . parse_location ( options . location )
elif options . cdrom :
cdrom = options . cdrom
if options . livecd :
no_install = True
elif options . pxe :
install_bootdev = " network "
elif installdata . is_set :
pass
elif ( options . import_install or
options . xmlonly or
options . boot ) :
no_install = True
installer = virtinst . Installer ( guest . conn ,
cdrom = cdrom ,
location = location ,
location_kernel = location_kernel ,
location_initrd = location_initrd ,
install_bootdev = install_bootdev ,
install_kernel = install_kernel ,
install_initrd = install_initrd ,
install_kernel_args = install_kernel_args ,
2020-02-03 15:23:05 +03:00
no_install = no_install ,
is_reinstall = is_reinstall )
2020-01-27 01:12:09 +03:00
if unattended_data :
installer . set_unattended_data ( unattended_data )
if extra_args :
installer . set_extra_args ( extra_args )
if options . initrd_inject :
installer . set_initrd_injections ( options . initrd_inject )
if options . autostart :
installer . autostart = True
if options . cloud_init :
cloudinit_data = cli . parse_cloud_init ( options . cloud_init )
installer . set_cloudinit_data ( cloudinit_data )
return installer
def set_cli_defaults ( options , guest ) :
if not guest . name :
default_name = virtinst . Guest . generate_name ( guest )
cli . print_stdout ( _ ( " Using default --name {vm_name} " ) . format (
vm_name = default_name ) )
guest . name = default_name
if guest . os . is_container ( ) :
if not memory_specified ( guest ) :
mbram = 1024
# LXC doesn't even do anything with memory settings, but libvirt
# XML requires it anyways. Fill in 64 MiB
cli . print_stdout (
_ ( " Using container default --memory {megabytes} " ) . format (
megabytes = mbram ) )
guest . currentMemory = mbram * 1024
return
if ( options . unattended and
guest . osinfo . is_windows ( ) and
guest . osinfo . supports_unattended_drivers ( guest . os . arch ) ) :
guest . add_extra_drivers (
guest . osinfo . get_pre_installable_devices ( guest . os . arch ) )
res = guest . osinfo . get_recommended_resources ( )
storage = res . get_recommended_storage ( guest . os . arch )
ram = res . get_recommended_ram ( guest . os . arch )
ncpus = res . get_recommended_ncpus ( guest . os . arch )
if ram and not memory_specified ( guest ) :
mbram = str ( ram / ( 1024 * 1024 ) ) . rstrip ( " 0 " ) . rstrip ( " . " )
cli . print_stdout (
_ ( " Using {os_name} default --memory {megabytes} " ) . format (
os_name = guest . osinfo . name , megabytes = mbram ) )
guest . currentMemory = ram / / 1024
if ncpus :
# We need to do this upfront, so we don't incorrectly set guest.vcpus
guest . sync_vcpus_topology ( )
if not guest . vcpus :
# I don't think we need to print anything here as this was never
# a required value.
guest . vcpus = ncpus
if storage and not storage_specified ( options , guest ) :
diskstr = ' size= %d ' % ( storage / / ( 1024 * * 3 ) )
cli . print_stdout (
_ ( " Using {os_name} default --disk {disk_options} " . format (
os_name = guest . osinfo . name , disk_options = diskstr ) ) )
options . disk = [ diskstr ]
cli . ParserDisk ( diskstr , guest = guest ) . parse ( None )
def set_explicit_guest_options ( options , guest ) :
if options . name :
guest . name = options . name
options . name = None
if options . uuid :
guest . uuid = options . uuid
options . uuid = None
if options . description :
guest . description = options . description
options . description = None
if options . os_type :
guest . os . os_type = options . os_type
options . os_type = None
if options . virt_type :
guest . type = options . virt_type
options . virt_type = None
if options . arch :
guest . os . arch = options . arch
options . arch = None
if options . machine :
guest . os . machine = options . machine
options . machine = None
def installer_detect_distro ( guest , installer , osdata ) :
2020-09-15 03:28:02 +03:00
os_set = False
2020-01-27 01:12:09 +03:00
try :
# OS name has to be set firstly whenever --os-variant is passed,
# otherwise it won't be respected when the installer creates the
# Distro Store.
2020-09-15 02:05:58 +03:00
if osdata . get_name ( ) :
2020-09-15 03:28:02 +03:00
os_set = True
2020-09-15 02:05:58 +03:00
guest . set_os_name ( osdata . get_name ( ) )
2020-01-27 01:12:09 +03:00
# This also validates the install location
autodistro = installer . detect_distro ( guest )
2020-09-15 02:05:58 +03:00
if osdata . is_detect ( ) and autodistro :
2020-09-15 03:28:02 +03:00
os_set = True
2020-01-27 01:12:09 +03:00
guest . set_os_name ( autodistro )
except ValueError as e :
fail ( _ ( " Error validating install location: %s " ) % str ( e ) )
2020-09-15 03:28:02 +03:00
if not os_set and osdata . is_require ( ) :
fail ( _ ( " An --os-variant is required, but no value was set or detected. " ) )
2020-01-27 01:12:09 +03:00
2020-02-03 15:23:05 +03:00
def _build_options_guest ( conn , options ) :
2020-01-27 01:12:09 +03:00
guest = Guest ( conn )
guest . skip_default_osinfo = True
# Fill in guest from the command line content
set_explicit_guest_options ( options , guest )
cli . parse_option_strings ( options , guest , None )
2020-09-10 20:52:07 +03:00
cli . parse_xmlcli ( guest , options )
2020-01-27 01:12:09 +03:00
# Call set_capabilities_defaults explicitly here rather than depend
# on set_defaults calling it. Installer setup needs filled in values.
# However we want to do it after parse_option_strings to ensure
# we are operating on any arch/os/type values passed in with --boot
guest . set_capabilities_defaults ( )
2020-09-10 20:52:07 +03:00
2020-02-03 15:23:05 +03:00
return guest
2020-01-27 01:12:09 +03:00
2020-02-03 15:23:05 +03:00
def build_guest_instance ( conn , options ) :
installdata = cli . parse_install ( options . install )
2020-09-15 02:05:58 +03:00
osdata = cli . parse_os_variant ( options . os_variant or installdata . os )
2020-02-03 15:23:05 +03:00
if options . reinstall :
2020-03-30 23:04:12 +03:00
dummy1 , guest , dummy2 = cli . get_domain_and_guest ( conn , options . reinstall )
2020-02-03 15:23:05 +03:00
else :
guest = _build_options_guest ( conn , options )
installer = build_installer ( options , guest , installdata )
# Set guest osname, from commandline or detected from media
2020-01-27 01:12:09 +03:00
guest . set_default_os_name ( )
installer_detect_distro ( guest , installer , osdata )
2020-02-03 15:23:05 +03:00
if not options . reinstall :
set_cli_defaults ( options , guest )
2020-01-27 01:12:09 +03:00
installer . set_install_defaults ( guest )
for path in installer . get_search_paths ( guest ) :
cli . check_path_search ( guest . conn , path )
2020-02-03 15:23:05 +03:00
if not options . reinstall :
# cli specific disk validation
for disk in guest . devices . disk :
cli . validate_disk ( disk )
2020-07-06 01:39:15 +03:00
for net in guest . devices . interface :
cli . validate_mac ( net . conn , net . macaddr )
2020-01-27 01:12:09 +03:00
validate_required_options ( options , guest , installer )
show_guest_warnings ( options , guest , osdata )
return guest , installer
###########################
# Install process helpers #
###########################
def _sleep ( secs ) :
2020-07-18 01:46:54 +03:00
if not virtinst . xmlutil . in_testsuite ( ) :
2020-01-27 01:12:09 +03:00
time . sleep ( secs ) # pragma: no cover
2020-09-11 02:45:37 +03:00
def _set_default_wait ( autoconsole , options ) :
if ( options . wait is not None or
autoconsole . has_console_cb ( ) or
not autoconsole . is_default ( ) ) :
return
# If there isn't any console to actually connect up,
# default to --wait -1 to get similarish behavior
log . warning ( _ ( " No console to launch for the guest, "
" defaulting to --wait -1 " ) )
options . wait = - 1
2020-01-27 01:12:09 +03:00
class WaitHandler :
"""
Helper class for handling the - - wait option sleeping and time tracking
"""
def __init__ ( self , wait ) :
self . wait_is_requested = False
self . _wait_mins = 0
self . _start_time = 0
if wait is not None :
self . wait_is_requested = True
self . _wait_mins = wait
@property
def wait_for_console_to_exit ( self ) :
# If --wait specified, we don't want the default behavior of waiting
# for virt-viewer to exit, we want to launch it, then manually count
# down time for ourselves
return not self . wait_is_requested
@property
def _wait_forever ( self ) :
return self . _wait_mins < 0
@property
def _wait_secs ( self ) :
return self . _wait_mins * 60
def start ( self ) :
self . _start_time = time . time ( )
def get_time_string ( self ) :
if self . _wait_forever :
2020-07-14 10:41:57 +03:00
return _ ( " Waiting for the installation to complete. " )
2020-07-14 10:41:58 +03:00
return ngettext ( " Waiting %(minutes)d minute for the installation to complete. " ,
" Waiting %(minutes)d minutes for the installation to complete. " ,
self . _wait_mins ) % { " minutes " : self . _wait_mins }
2020-01-27 01:12:09 +03:00
def wait ( self ) :
"""
sleep 1 second , then teturn True if wait time has expired
"""
_sleep ( 1 )
if self . _wait_forever :
2020-07-18 01:46:54 +03:00
if virtinst . xmlutil . in_testsuite ( ) :
2020-01-27 01:12:09 +03:00
return True
return False # pragma: no cover
time_elapsed = ( time . time ( ) - self . _start_time )
2020-07-18 01:46:54 +03:00
return ( time_elapsed > = self . _wait_secs ) or virtinst . xmlutil . in_testsuite ( )
2020-01-27 01:12:09 +03:00
def _print_cloudinit_passwd ( installer ) :
passwd = installer . get_generated_password ( )
if not passwd :
return
print_stdout ( _ ( " Password for first root login is: %s " ) % passwd ,
do_force = True , do_log = False )
stdins = [ sys . stdin ]
timeout = 10
if sys . stdin . closed or not sys . stdin . isatty ( ) :
2020-07-18 01:46:54 +03:00
if not virtinst . xmlutil . in_testsuite ( ) : # pragma: no cover
2020-01-27 01:12:09 +03:00
return
stdins = [ ]
timeout = .0001
sys . stdout . write (
_ ( " Installation will continue in 10 seconds "
" (press Enter to skip)... " ) )
sys . stdout . flush ( )
select . select ( stdins , [ ] , [ ] , timeout )
2020-09-11 02:45:37 +03:00
def _connect_console ( guest , instdomain , autoconsole , wait ) :
"""
Launched the passed console callback for the already defined
domain . If domain isn ' t running, return an error.
"""
console_cb = autoconsole . get_console_cb ( )
if not console_cb :
return
2020-01-27 01:12:09 +03:00
2020-09-11 02:45:37 +03:00
child = console_cb ( guest )
if not wait :
return
2020-01-27 01:12:09 +03:00
2020-09-11 02:45:37 +03:00
# If we connected the console, wait for it to finish
2020-01-27 01:12:09 +03:00
try :
2020-09-11 02:45:37 +03:00
errcode = os . waitpid ( child , 0 ) [ 1 ]
except OSError as e : # pragma: no cover
log . debug ( " waitpid error: %s " , e )
2020-01-27 01:12:09 +03:00
2020-09-11 02:45:37 +03:00
if errcode :
log . warning ( _ ( " Console command returned failure. " ) )
2020-01-27 01:12:09 +03:00
2020-09-11 02:45:37 +03:00
instdomain . handle_destroy_on_exit ( )
2020-01-27 01:12:09 +03:00
2020-09-11 02:45:37 +03:00
class _InstalledDomain :
2020-01-27 01:12:09 +03:00
"""
2020-09-11 02:45:37 +03:00
Wrapper for the domain object after the initial install creation
2020-01-27 01:12:09 +03:00
"""
2020-09-11 02:45:37 +03:00
def __init__ ( self , domain , transient , destroy_on_exit ) :
self . _domain = domain
self . _transient = transient
self . _destroy_on_exit = destroy_on_exit
if destroy_on_exit :
atexit . register ( _destroy_on_exit , domain )
def handle_destroy_on_exit ( self ) :
if self . _destroy_on_exit and self . _domain . isActive ( ) :
log . debug ( " console exited and destroy_on_exit passed, destroying " )
self . _domain . destroy ( )
2020-09-11 15:57:08 +03:00
def domain_was_destroyed ( self ) :
try :
state , reason = self . _domain . state ( )
return ( state == libvirt . VIR_DOMAIN_SHUTOFF and
reason in [ libvirt . VIR_DOMAIN_SHUTOFF_DESTROYED ,
libvirt . VIR_DOMAIN_SHUTOFF_SAVED ] )
except Exception : # pragma: no cover
log . debug ( " Error checking VM shutdown reason " , exc_info = True )
2020-09-11 02:45:37 +03:00
def check_inactive ( self ) :
2020-01-27 01:12:09 +03:00
try :
2020-09-11 02:45:37 +03:00
dominfo = self . _domain . info ( )
2020-01-27 01:12:09 +03:00
state = dominfo [ 0 ]
if state == libvirt . VIR_DOMAIN_CRASHED :
fail ( _ ( " Domain has crashed. " ) ) # pragma: no cover
2020-09-11 02:45:37 +03:00
return not self . _domain . isActive ( )
2020-01-27 01:12:09 +03:00
except libvirt . libvirtError as e :
2020-09-11 02:45:37 +03:00
if ( self . _transient and
e . get_error_code ( ) == libvirt . VIR_ERR_NO_DOMAIN ) :
2020-01-27 01:12:09 +03:00
log . debug ( " transient VM shutdown and disappeared. " )
return True
raise # pragma: no cover
2020-09-11 02:45:37 +03:00
def _wait_for_domain ( installer , instdomain , autoconsole , waithandler ) :
"""
Make sure domain ends up in expected state , and wait if for install
to complete if requested
"""
if instdomain . check_inactive ( ) :
2020-01-27 01:12:09 +03:00
return
2020-09-11 02:45:37 +03:00
if bool ( autoconsole . get_console_cb ( ) ) :
2020-01-27 01:12:09 +03:00
# We are trying to detect if the VM shutdown, or the user
# just closed the console and the VM is still running. In the
# the former case, libvirt may not have caught up yet with the
# VM having exited, so wait a bit and check again
_sleep ( 2 )
2020-09-11 02:45:37 +03:00
if instdomain . check_inactive ( ) :
2020-01-27 01:12:09 +03:00
return # pragma: no cover
# If we reach here, the VM still appears to be running.
2020-02-02 16:49:06 +03:00
msg = " \n "
msg + = _ ( " Domain is still running. Installation may be in progress. " )
2020-01-27 01:12:09 +03:00
if not waithandler . wait_is_requested :
# User either:
# used --noautoconsole
# killed console and guest is still running
if not installer . has_install_phase ( ) :
return
2020-02-02 16:49:06 +03:00
msg + = " \n "
msg + = _ ( " You can reconnect to the console to complete the "
" installation process. " )
print_stdout ( msg )
2020-01-27 01:12:09 +03:00
sys . exit ( 0 )
2020-02-02 16:49:06 +03:00
print_stdout ( msg )
2020-01-27 01:12:09 +03:00
print_stdout ( waithandler . get_time_string ( ) )
# Wait loop
while True :
2020-09-11 02:45:37 +03:00
if instdomain . check_inactive ( ) : # pragma: no cover
2020-01-27 01:12:09 +03:00
print_stdout ( _ ( " Domain has shutdown. Continuing. " ) )
break
done = waithandler . wait ( )
if done :
print_stdout (
_ ( " Installation has exceeded specified time limit. "
" Exiting application. " ) )
sys . exit ( 1 )
2020-09-11 15:57:08 +03:00
def _testsuite_hack_destroy ( domain ) :
# Trigger specific behavior checking if user destroyed the domain
if os . environ . get ( " VIRTINST_TESTSUITE_HACK_DESTROY " ) :
domain . destroy ( )
2020-09-11 02:45:37 +03:00
def _process_domain ( domain , guest , installer , waithandler , autoconsole ,
transient , destroy_on_exit , noreboot ) :
"""
Handle the pieces of the install process after the initial VM startup
"""
instdomain = _InstalledDomain ( domain , transient , destroy_on_exit )
_connect_console ( guest , instdomain , autoconsole ,
waithandler . wait_for_console_to_exit )
2020-09-11 15:57:08 +03:00
_testsuite_hack_destroy ( domain )
2020-09-11 02:45:37 +03:00
_wait_for_domain ( installer , instdomain , autoconsole , waithandler )
print_stdout ( _ ( " Domain creation completed. " ) )
if transient :
return
if domain . isActive ( ) :
return
if noreboot or not installer . has_install_phase ( ) :
print_stdout ( # pragma: no cover
_ ( " You can restart your domain by running: \n %s " ) %
cli . virsh_start_cmd ( guest ) )
return
2020-09-11 15:57:08 +03:00
if instdomain . domain_was_destroyed ( ) and not destroy_on_exit :
print_stdout ( _ ( " User stopped the VM. Not rebooting. " ) )
return
2020-09-11 02:45:37 +03:00
print_stdout ( _ ( " Restarting guest. " ) )
domain . create ( )
_connect_console ( guest , instdomain , autoconsole , True )
def start_install ( guest , installer , options ) :
"""
Process all the install workflow specific options , and kick off
the Installer process
"""
autoconsole = cli . parse_autoconsole ( options , guest , installer )
show_console_warnings ( installer , autoconsole )
_set_default_wait ( autoconsole , options )
waithandler = WaitHandler ( options . wait )
meter = cli . get_meter ( )
# we've got everything -- try to start the install
print_stdout ( _ ( " \n Starting install... " ) )
_print_cloudinit_passwd ( installer )
waithandler . start ( )
try :
try :
domain = installer . start_install (
guest , meter = meter ,
doboot = not options . noreboot ,
transient = options . transient )
except : # noqa
virtinst . Installer . cleanup_created_disks ( guest , meter )
raise
_process_domain ( domain , guest , installer ,
waithandler , autoconsole , options . transient ,
options . destroy_on_exit , options . noreboot )
if virtinst . xmlutil . in_testsuite ( ) and options . destroy_on_exit :
# Helps with unit testing
_destroy_on_exit ( domain )
except KeyboardInterrupt : # pragma: no cover
log . debug ( " " , exc_info = True )
print_stderr ( _ ( " Domain install interrupted. " ) )
raise
except Exception as e :
fail ( e , do_exit = False )
cli . install_fail ( guest )
2020-01-27 01:12:09 +03:00
########################
# XML printing helpers #
########################
def xml_to_print ( guest , installer , xmlonly , dry ) :
start_xml , final_xml = installer . start_install (
guest , dry = dry , return_xml = True )
if not start_xml :
start_xml = final_xml
final_xml = None
if dry and not xmlonly :
print_stdout ( _ ( " Dry run completed successfully " ) )
return
if xmlonly not in [ False , " 1 " , " 2 " , " all " ] :
fail ( _ ( " Unknown XML step request ' %s ' , must be 1, 2, or all " ) %
xmlonly )
if xmlonly == " 1 " :
return start_xml
if xmlonly == " 2 " :
if not final_xml :
fail ( _ ( " Requested installation does not have XML step 2 " ) )
return final_xml
# "all" case
xml = start_xml
if final_xml :
xml + = final_xml
return xml
#######################
# CLI option handling #
#######################
def parse_args ( ) :
parser = cli . setupParser (
" %(prog)s --name NAME --memory MB STORAGE INSTALL [options] " ,
_ ( " Create a new virtual machine from specified install media. " ) ,
introspection_epilog = True )
cli . add_connect_option ( parser )
geng = parser . add_argument_group ( _ ( " General Options " ) )
geng . add_argument ( " -n " , " --name " ,
help = _ ( " Name of the guest instance " ) )
cli . add_memory_option ( geng , backcompat = True )
cli . vcpu_cli_options ( geng )
cli . add_metadata_option ( geng )
2020-09-10 20:52:07 +03:00
cli . add_xml_option ( geng )
2020-01-27 01:12:09 +03:00
geng . add_argument ( " -u " , " --uuid " , help = argparse . SUPPRESS )
geng . add_argument ( " --description " , help = argparse . SUPPRESS )
insg = parser . add_argument_group ( _ ( " Installation Method Options " ) )
insg . add_argument ( " -c " , dest = " cdrom_short " , help = argparse . SUPPRESS )
insg . add_argument ( " --cdrom " , help = _ ( " CD-ROM installation media " ) )
insg . add_argument ( " -l " , " --location " ,
help = _ ( " Distro install URL, eg. https://host/path. See man "
" page for specific distro examples. " ) )
insg . add_argument ( " --pxe " , action = " store_true " ,
help = _ ( " Boot from the network using the PXE protocol " ) )
insg . add_argument ( " --import " , action = " store_true " , dest = " import_install " ,
help = _ ( " Build guest around an existing disk image " ) )
insg . add_argument ( " --livecd " , action = " store_true " , help = argparse . SUPPRESS )
insg . add_argument ( " -x " , " --extra-args " , action = " append " ,
help = _ ( " Additional arguments to pass to the install kernel "
" booted from --location " ) )
insg . add_argument ( " --initrd-inject " , action = " append " ,
help = _ ( " Add given file to root of initrd from --location " ) )
insg . add_argument ( " --unattended " , nargs = " ? " , const = 1 ,
help = _ ( " Perform an unattended installation " ) )
insg . add_argument ( " --install " ,
help = _ ( " Specify fine grained install options " ) )
2020-02-03 15:23:05 +03:00
insg . add_argument ( " --reinstall " , metavar = " DOMAIN " ,
help = _ ( " Reinstall existing VM. Only install options are applied, "
" all other VM configuration options are ignored. " ) )
2020-01-27 01:12:09 +03:00
insg . add_argument ( " --cloud-init " , nargs = " ? " , const = 1 ,
help = _ ( " Perform a cloud image installation, configuring cloud-init " ) )
# Takes a URL and just prints to stdout the detected distro name
insg . add_argument ( " --test-media-detection " , help = argparse . SUPPRESS )
# Helper for cli testing, fills in standard stub options
insg . add_argument ( " --test-stub-command " , action = " store_true " ,
help = argparse . SUPPRESS )
cli . add_boot_options ( insg )
insg . add_argument ( " --init " , help = argparse . SUPPRESS )
osg = cli . add_os_variant_option ( parser , virtinstall = True )
osg . add_argument ( " --os-type " , dest = " old_os_type " , help = argparse . SUPPRESS )
devg = parser . add_argument_group ( _ ( " Device Options " ) )
cli . add_disk_option ( devg )
cli . add_net_option ( devg )
cli . add_gfx_option ( devg )
cli . add_device_options ( devg , sound_back_compat = True )
# Deprecated device options
devg . add_argument ( " -f " , " --file " , dest = " file_paths " , action = " append " ,
help = argparse . SUPPRESS )
devg . add_argument ( " -s " , " --file-size " , type = float ,
action = " append " , dest = " disksize " ,
help = argparse . SUPPRESS )
devg . add_argument ( " --nonsparse " , action = " store_false " ,
default = True , dest = " sparse " ,
help = argparse . SUPPRESS )
devg . add_argument ( " --nodisks " , action = " store_true " , help = argparse . SUPPRESS )
devg . add_argument ( " --nonetworks " , action = " store_true " ,
help = argparse . SUPPRESS )
devg . add_argument ( " -b " , " --bridge " , action = " append " ,
help = argparse . SUPPRESS )
devg . add_argument ( " -m " , " --mac " , action = " append " , help = argparse . SUPPRESS )
devg . add_argument ( " --vnc " , action = " store_true " , help = argparse . SUPPRESS )
devg . add_argument ( " --vncport " , type = int , help = argparse . SUPPRESS )
devg . add_argument ( " --vnclisten " , help = argparse . SUPPRESS )
devg . add_argument ( " -k " , " --keymap " , help = argparse . SUPPRESS )
devg . add_argument ( " --sdl " , action = " store_true " , help = argparse . SUPPRESS )
devg . add_argument ( " --nographics " , action = " store_true " ,
help = argparse . SUPPRESS )
gxmlg = parser . add_argument_group ( _ ( " Guest Configuration Options " ) )
cli . add_guest_xml_options ( gxmlg )
virg = parser . add_argument_group ( _ ( " Virtualization Platform Options " ) )
ostypeg = virg . add_mutually_exclusive_group ( )
ostypeg . add_argument ( " -v " , " --hvm " ,
action = " store_const " , const = " hvm " , dest = " os_type " ,
help = _ ( " This guest should be a fully virtualized guest " ) )
ostypeg . add_argument ( " -p " , " --paravirt " ,
action = " store_const " , const = " xen " , dest = " os_type " ,
help = _ ( " This guest should be a paravirtualized guest " ) )
ostypeg . add_argument ( " --container " ,
action = " store_const " , const = " exe " , dest = " os_type " ,
help = _ ( " This guest should be a container guest " ) )
virg . add_argument ( " --virt-type " ,
help = _ ( " Hypervisor name to use (kvm, qemu, xen, ...) " ) )
virg . add_argument ( " --arch " , help = _ ( " The CPU architecture to simulate " ) )
virg . add_argument ( " --machine " , help = _ ( " The machine type to emulate " ) )
virg . add_argument ( " --accelerate " , action = " store_true " ,
help = argparse . SUPPRESS )
virg . add_argument ( " --noapic " , action = " store_true " ,
default = False , help = argparse . SUPPRESS )
virg . add_argument ( " --noacpi " , action = " store_true " ,
default = False , help = argparse . SUPPRESS )
misc = parser . add_argument_group ( _ ( " Miscellaneous Options " ) )
misc . add_argument ( " --autostart " , action = " store_true " , default = False ,
help = _ ( " Have domain autostart on host boot up. " ) )
misc . add_argument ( " --transient " , action = " store_true " , default = False ,
help = _ ( " Create a transient domain. " ) )
misc . add_argument ( " --destroy-on-exit " , action = " store_true " , default = False ,
help = _ ( " Force power off the domain when the console "
" viewer is closed. " ) )
misc . add_argument ( " --wait " , type = int , const = - 1 , nargs = " ? " ,
help = _ ( " Minutes to wait for install to complete. " ) )
cli . add_misc_options ( misc , prompt = True , printxml = True , printstep = True ,
noreboot = True , dryrun = True , noautoconsole = True )
cli . autocomplete ( parser )
return parser . parse_args ( )
###################
# main() handling #
###################
# Catchall for destroying the VM on ex. ctrl-c
def _destroy_on_exit ( domain ) :
try :
isactive = bool ( domain and domain . isActive ( ) )
if isactive :
domain . destroy ( ) # pragma: no cover
except libvirt . libvirtError as e : # pragma: no cover
if e . get_error_code ( ) != libvirt . VIR_ERR_NO_DOMAIN :
log . debug ( " Error invoking atexit destroy_on_exit " ,
exc_info = True )
def set_test_stub_options ( options ) : # pragma: no cover
# Set some basic options that will let virt-install succeed. Helps
# save boiler plate typing when testing new command line additions
if not options . test_stub_command :
return
options . import_install = True
if not options . connect :
options . connect = " test:///default "
if not options . name :
options . name = " test-stub-command "
if not options . memory :
options . memory = " 256 "
if not options . disk :
options . disk = " none "
if not options . graphics :
options . graphics = " none "
if not options . os_variant :
options . os_variant = " fedora27 "
def main ( conn = None ) :
cli . earlyLogging ( )
options = parse_args ( )
# Default setup options
convert_old_printxml ( options )
options . quiet = ( options . xmlonly or
options . test_media_detection or options . quiet )
cli . setupLogging ( " virt-install " , options . debug , options . quiet )
if cli . check_option_introspection ( options ) :
return 0
check_cdrom_option_error ( options )
cli . convert_old_force ( options )
cli . parse_check ( options . check )
cli . set_prompt ( options . prompt )
convert_old_memory ( options )
convert_old_sound ( options )
convert_old_networks ( options )
convert_old_graphics ( options )
convert_old_disks ( options )
convert_old_features ( options )
convert_old_cpuset ( options )
convert_old_init ( options )
convert_wait_zero ( options )
set_test_stub_options ( options )
convert_old_os_options ( options )
conn = cli . getConnection ( options . connect , conn = conn )
if options . test_media_detection :
do_test_media_detection ( conn , options )
return 0
guest , installer = build_guest_instance ( conn , options )
if options . xmlonly or options . dry :
xml = xml_to_print ( guest , installer , options . xmlonly , options . dry )
if xml :
print_stdout ( xml , do_force = True )
else :
start_install ( guest , installer , options )
return 0
def runcli ( ) : # pragma: no cover
try :
sys . exit ( main ( ) )
except SystemExit as sys_e :
sys . exit ( sys_e . code )
except KeyboardInterrupt :
log . debug ( " " , exc_info = True )
print_stderr ( _ ( " Installation aborted at user request " ) )
except Exception as main_e :
fail ( main_e )