2013-03-18 01:06:52 +04:00
#
# Common code for all guests
#
# Copyright 2006-2009 Red Hat, Inc.
# Jeremy Katz <katzj@redhat.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA.
import os
import platform
import logging
import copy
2013-04-11 03:48:07 +04:00
from virtinst import util
2013-03-18 01:06:52 +04:00
import virtinst
2013-07-14 02:56:09 +04:00
from virtinst . xmlbuilder import XMLBuilder , XMLProperty
2013-04-11 03:48:07 +04:00
from virtinst . Boot import Boot
2013-03-18 01:06:52 +04:00
XEN_SCRATCH = " /var/lib/xen "
LIBVIRT_SCRATCH = " /var/lib/libvirt/boot "
2013-04-11 19:11:21 +04:00
2013-07-14 02:56:09 +04:00
class Installer ( XMLBuilder ) :
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 ' )
"""
_dumpxml_xpath = " /domain/os "
2013-04-15 00:31:18 +04:00
_has_install_phase = True
2013-07-06 04:14:57 +04:00
def __init__ ( self , conn , type = " xen " , location = None ,
extraargs = None , os_type = None ,
2013-07-06 22:12:13 +04:00
parsexml = None , parsexmlnode = None ) :
2013-04-12 17:51:26 +04:00
# pylint: disable=W0622
# Redefining built-in 'type', but it matches the XML so keep it
2013-07-14 02:56:09 +04:00
XMLBuilder . __init__ ( self , conn , parsexml ,
2013-07-06 22:12:13 +04:00
parsexmlnode )
2013-03-18 01:06:52 +04:00
self . _type = None
self . _location = None
self . _initrd_injections = [ ]
self . _cdrom = False
self . _os_type = None
self . _scratchdir = None
self . _arch = None
self . _machine = None
self . _loader = None
self . _init = None
self . _install_bootconfig = Boot ( self . conn )
self . _bootconfig = Boot ( self . conn , parsexml , parsexmlnode )
# Devices created/added during the prepare() stage
self . install_devices = [ ]
if self . _is_parse ( ) :
return
2013-07-06 22:12:13 +04:00
self . _arch = self . conn . caps . host . arch
2013-03-18 01:06:52 +04:00
if type is None :
type = " xen "
self . type = type
if not os_type is None :
self . os_type = os_type
else :
self . os_type = " xen "
if not location is None :
self . location = location
self . extraargs = extraargs
self . _tmpfiles = [ ]
self . _tmpvols = [ ]
def _get_bootconfig ( self ) :
return self . _bootconfig
bootconfig = property ( _get_bootconfig )
# Hypervisor name (qemu, kvm, xen, lxc, etc.)
def get_type ( self ) :
return self . _type
def set_type ( self , val ) :
self . _type = val
2013-07-14 02:56:09 +04:00
type = XMLProperty ( get_type , set_type ,
2013-03-18 01:06:52 +04:00
xpath = " ./@type " )
# Virtualization type ('xen' == xen paravirt, or 'hvm)
def get_os_type ( self ) :
return self . _os_type
def set_os_type ( self , val ) :
# Older libvirt back compat: if user specifies 'linux', convert
# internally to newer equivalent value 'xen'
if val == " linux " :
val = " xen "
# XXX: Need to validate this: have some whitelist based on caps?
self . _os_type = val
2013-07-14 02:56:09 +04:00
os_type = XMLProperty ( get_os_type , set_os_type ,
2013-03-18 01:06:52 +04:00
xpath = " ./os/type " )
def get_arch ( self ) :
return self . _arch
def set_arch ( self , val ) :
# XXX: Sanitize to a consisten value (i368 -> i686)
# XXX: Validate against caps
self . _arch = val
2013-07-14 02:56:09 +04:00
arch = XMLProperty ( get_arch , set_arch ,
2013-03-18 01:06:52 +04:00
xpath = " ./os/type/@arch " )
def _get_machine ( self ) :
return self . _machine
def _set_machine ( self , val ) :
self . _machine = val
2013-07-14 02:56:09 +04:00
machine = XMLProperty ( _get_machine , _set_machine ,
2013-03-18 01:06:52 +04:00
xpath = " ./os/type/@machine " )
def _get_loader ( self ) :
return self . _loader
def _set_loader ( self , val ) :
self . _loader = val
2013-07-14 02:56:09 +04:00
loader = XMLProperty ( _get_loader , _set_loader ,
2013-03-18 01:06:52 +04:00
xpath = " ./os/loader " )
def _get_init ( self ) :
return self . _init
def _set_init ( self , val ) :
self . _init = val
2013-07-14 02:56:09 +04:00
init = XMLProperty ( _get_init , _set_init ,
2013-03-18 01:06:52 +04:00
xpath = " ./os/init " )
def get_scratchdir ( self ) :
if not self . scratchdir_required ( ) :
return None
if not self . _scratchdir :
self . _scratchdir = self . _get_scratchdir ( )
logging . debug ( " scratchdir= %s " , self . _scratchdir )
return self . _scratchdir
scratchdir = property ( get_scratchdir )
def get_cdrom ( self ) :
return self . _cdrom
def set_cdrom ( self , enable ) :
if enable not in [ True , False ] :
raise ValueError ( _ ( " Guest.cdrom must be a boolean type " ) )
self . _cdrom = enable
cdrom = property ( get_cdrom , set_cdrom )
def get_location ( self ) :
return self . _location
def set_location ( self , val ) :
self . _location = val
location = property ( get_location , set_location )
def get_initrd_injections ( self ) :
return self . _initrd_injections
def set_initrd_injections ( self , val ) :
self . _initrd_injections = val
initrd_injections = property ( get_initrd_injections , set_initrd_injections )
# extra arguments to pass to the guest installer
def get_extra_args ( self ) :
return self . _install_bootconfig . kernel_args
def set_extra_args ( self , val ) :
self . _install_bootconfig . kernel_args = val
extraargs = property ( get_extra_args , set_extra_args )
# Public helper methods
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
"""
return False
def is_hvm ( self ) :
return self . os_type == " hvm "
def is_xenpv ( self ) :
return self . os_type in [ " xen " , " linux " ]
def is_container ( self ) :
return self . os_type == " exe "
# Private methods
def _get_system_scratchdir ( self ) :
if platform . system ( ) == " SunOS " :
return " /var/tmp "
if self . type == " test " :
return " /tmp "
elif self . type == " xen " :
return XEN_SCRATCH
else :
return LIBVIRT_SCRATCH
def _get_scratchdir ( self ) :
scratch = None
2013-07-06 04:36:28 +04:00
if not self . conn . is_session_uri ( ) :
2013-03-18 01:06:52 +04:00
scratch = self . _get_system_scratchdir ( )
if ( not scratch or
not os . path . exists ( scratch ) or
not os . access ( scratch , os . W_OK ) ) :
scratch = os . path . expanduser ( " ~/.virtinst/boot " )
if not os . path . exists ( scratch ) :
os . makedirs ( scratch , 0751 )
return scratch
def _get_bootdev ( self , isinstall , guest ) :
raise NotImplementedError ( " Must be implemented in subclass " )
def _build_boot_order ( self , isinstall , guest ) :
bootorder = [ self . _get_bootdev ( isinstall , guest ) ]
# 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)
for disk in guest . get_devices ( " disk " ) :
if disk . device == disk . DEVICE_DISK :
bootdev = self . bootconfig . BOOT_DEVICE_HARDDISK
if bootdev not in bootorder :
bootorder . append ( bootdev )
break
return bootorder
def _get_default_init ( self , guest ) :
if not self . is_container ( ) :
return
for fs in guest . get_devices ( " filesystem " ) :
if fs . target == " / " :
return " /sbin/init "
return " /bin/sh "
2013-07-13 18:09:00 +04:00
def _make_cdrom_dev ( self , path ) :
dev = virtinst . VirtualDisk ( self . conn )
dev . path = path
dev . device = dev . DEVICE_CDROM
dev . read_only = True
dev . validate ( )
return dev
2013-03-18 01:06:52 +04:00
def _get_osblob_helper ( self , guest , isinstall , bootconfig ) :
arch = self . arch
machine = self . machine
hvtype = self . type
loader = self . loader
os_type = self . os_type
init = self . init or self . _get_default_init ( guest )
hvxen = ( hvtype == " xen " )
if not loader and self . is_hvm ( ) and hvxen :
loader = " /usr/lib/xen/boot/hvmloader "
# Use older libvirt 'linux' value for back compat
if os_type == " xen " and hvxen :
os_type = " linux "
if ( not isinstall and
self . is_xenpv ( ) and
not self . bootconfig . kernel ) :
2013-07-06 04:14:57 +04:00
# This really should be provided by capabilites xml
return " <bootloader>/usr/bin/pygrub</bootloader> "
2013-03-18 01:06:52 +04:00
osblob = " <os> "
typexml = " <type "
if arch :
typexml + = " arch= ' %s ' " % arch
if machine :
typexml + = " machine= ' %s ' " % machine
typexml + = " > %s </type> " % os_type
2013-04-11 18:27:02 +04:00
osblob = util . xml_append ( osblob , typexml )
2013-03-18 01:06:52 +04:00
if init :
2013-04-11 18:27:02 +04:00
osblob = util . xml_append ( osblob ,
2013-03-18 01:06:52 +04:00
" <init> %s </init> " %
2013-04-11 18:27:02 +04:00
util . xml_escape ( init ) )
2013-03-18 01:06:52 +04:00
if loader :
2013-04-11 18:27:02 +04:00
osblob = util . xml_append ( osblob ,
2013-03-18 01:06:52 +04:00
" <loader> %s </loader> " %
2013-04-11 18:27:02 +04:00
util . xml_escape ( loader ) )
2013-03-18 01:06:52 +04:00
if not self . is_container ( ) :
2013-04-11 18:27:02 +04:00
osblob = util . xml_append ( osblob , bootconfig . get_xml_config ( ) )
osblob = util . xml_append ( osblob , " </os> " )
2013-03-18 01:06:52 +04:00
return osblob
# Method definitions
def _get_xml_config ( self , guest , isinstall ) :
"""
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
@type guest : L { Guest }
@param isinstall : Whether we want xml for the ' install ' phase or the
' post-install ' phase .
@type isinstall : C { bool }
"""
2013-04-12 17:51:26 +04:00
# pylint: disable=W0221
# Argument number differs from overridden method
2013-03-18 01:06:52 +04:00
if isinstall :
bootconfig = self . _install_bootconfig
else :
bootconfig = self . bootconfig
if isinstall and not self . has_install_phase ( ) :
return
bootorder = self . _build_boot_order ( isinstall , guest )
bootconfig = copy . copy ( bootconfig )
if not bootconfig . bootorder :
bootconfig . bootorder = bootorder
return self . _get_osblob_helper ( guest , isinstall , bootconfig )
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 .
"""
2013-04-15 00:31:18 +04:00
return self . _has_install_phase
2013-03-18 01:06:52 +04:00
def cleanup ( self ) :
"""
Remove any temporary files retrieved during installation
"""
for f in self . _tmpfiles :
logging . debug ( " Removing " + f )
os . unlink ( f )
for vol in self . _tmpvols :
logging . debug ( " Removing volume ' %s ' " , vol . name ( ) )
vol . delete ( 0 )
self . _tmpvols = [ ]
self . _tmpfiles = [ ]
self . install_devices = [ ]
def prepare ( self , guest , meter ) :
"""
Fetch any files needed for installation .
@param guest : guest instance being installed
@type guest : L { Guest }
@param meter : progress meter
@type meter : Urlgrabber ProgressMeter
"""
raise NotImplementedError ( " Must be implemented in subclass " )
def detect_distro ( self ) :
"""
Attempt to detect the distro for the Installer ' s ' location ' . If
an error is encountered in the detection process ( or if detection
is not relevant for the Installer type ) , ( None , None ) is returned
@returns : ( distro type , distro variant ) tuple
"""
return ( None , None )
def guest_from_installer ( self ) :
"""
Return a L { Guest } instance wrapping the current installer .
If all the appropriate values are present in the installer
( conn , type , os_type , arch , machine ) , we have everything we need
to determine what L { Guest } class is expected and what default values
to pass it . This is a convenience method to save the API user from
having to enter all these known details twice .
"""
if not self . conn :
raise ValueError ( _ ( " A connection must be specified. " ) )
2013-07-06 22:12:13 +04:00
guest , domain = self . conn . caps . guest_lookup ( os_type = self . os_type ,
typ = self . type ,
arch = self . arch ,
machine = self . machine )
2013-03-18 01:06:52 +04:00
2013-07-03 21:37:00 +04:00
gobj = virtinst . Guest ( self . conn , installer = self )
2013-03-18 01:06:52 +04:00
gobj . arch = guest . arch
gobj . emulator = domain . emulator
self . loader = domain . loader
return gobj
2013-04-12 17:51:26 +04:00
2013-03-18 01:06:52 +04:00
class ContainerInstaller ( Installer ) :
2013-04-15 00:31:18 +04:00
_has_install_phase = False
2013-03-18 01:06:52 +04:00
def prepare ( self , guest , meter ) :
ignore = guest
ignore = meter
def _get_bootdev ( self , isinstall , guest ) :
ignore = isinstall
ignore = guest
return self . bootconfig . BOOT_DEVICE_HARDDISK