2013-03-17 17:06:52 -04:00
#
# Copyright 2006-2009 Red Hat, Inc.
# Daniel P. Berrange <berrange@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 logging
import os
import shutil
import subprocess
import tempfile
import urlgrabber
2013-04-10 19:48:07 -04:00
from virtinst import Storage
from virtinst import util
from virtinst import Installer
from virtinst . VirtualDisk import VirtualDisk
from virtinst import OSDistro
2013-03-17 17:06:52 -04:00
def _is_url ( url , is_local ) :
"""
Check if passed string is a ( pseudo ) valid http , ftp , or nfs url .
"""
if is_local and os . path . exists ( url ) :
if os . path . isdir ( url ) :
return True
else :
return False
return ( url . startswith ( " http:// " ) or url . startswith ( " ftp:// " ) or
url . startswith ( " nfs: " ) )
2013-04-13 14:34:52 -04:00
2013-03-17 17:06:52 -04:00
def _sanitize_url ( url ) :
"""
Do nothing for http or ftp , but make sure nfs is in the expected format
"""
if url . startswith ( " nfs:// " ) :
# Convert RFC compliant NFS nfs://server/path/to/distro
# to what mount/anaconda expect nfs:server:/path/to/distro
# and carry the latter form around internally
url = " nfs: " + url [ 6 : ]
# If we need to add the : after the server
index = url . find ( " / " , 4 )
if index == - 1 :
raise ValueError ( _ ( " Invalid NFS format: No path specified. " ) )
if url [ index - 1 ] != " : " :
url = url [ : index ] + " : " + url [ index : ]
return url
2013-04-13 14:34:52 -04:00
2013-03-17 17:06:52 -04:00
def _build_pool ( conn , meter , path ) :
2013-04-11 10:27:02 -04:00
pool = util . lookup_pool_by_path ( conn , path )
2013-03-17 17:06:52 -04:00
if pool :
logging . debug ( " Existing pool ' %s ' found for %s " , pool . name ( ) , path )
pool . refresh ( 0 )
return pool
2013-04-11 10:27:02 -04:00
name = util . generate_name ( " boot-scratch " ,
2013-03-17 17:06:52 -04:00
conn . storagePoolLookupByName )
logging . debug ( " Building storage pool: path= %s name= %s " , path , name )
2013-07-05 20:14:57 -04:00
poolbuild = Storage . DirectoryPool ( conn , name = name ,
2013-03-17 17:06:52 -04:00
target_path = path )
# Explicitly don't build? since if we are creating this directory
# we probably don't have correct perms
return poolbuild . install ( meter = meter , create = True , build = False ,
autostart = True )
def _upload_file ( conn , meter , destpool , src ) :
# Build stream object
stream = conn . newStream ( 0 )
def safe_send ( data ) :
while True :
ret = stream . send ( data )
if ret == 0 or ret == len ( data ) :
break
data = data [ ret : ]
if meter is None :
meter = urlgrabber . progress . BaseMeter ( )
# Build placeholder volume
size = os . path . getsize ( src )
basename = os . path . basename ( src )
2013-07-12 10:53:30 -04:00
poolpath = util . xpath ( destpool . XMLDesc ( 0 ) , " /pool/target/path " )
2013-03-17 17:06:52 -04:00
name = Storage . StorageVolume . find_free_name ( basename ,
pool_object = destpool )
if name != basename :
logging . debug ( " Generated non-colliding volume name %s " , name )
2013-07-13 10:09:00 -04:00
disk = VirtualDisk ( conn )
disk . path = os . path . join ( poolpath , name )
disk . set_create_storage ( size = ( float ( size ) / 1024.0 / 1024.0 / 1024.0 ) ,
sparse = True )
disk . validate ( )
2013-03-17 17:06:52 -04:00
2013-07-05 20:14:57 -04:00
disk . setup ( meter = meter )
2013-07-13 10:09:00 -04:00
vol = disk . get_vol_object ( )
2013-03-17 17:06:52 -04:00
if not vol :
raise RuntimeError ( _ ( " Failed to lookup scratch media volume " ) )
try :
# Register upload
offset = 0
length = size
flags = 0
stream . upload ( vol , offset , length , flags )
# Open source file
fileobj = file ( src , " r " )
# Start transfer
total = 0
meter . start ( size = size ,
text = _ ( " Transferring %s " ) % os . path . basename ( src ) )
while True :
2013-04-13 14:34:52 -04:00
# blocksize = (1024 ** 2)
2013-03-17 17:06:52 -04:00
blocksize = 1024
data = fileobj . read ( blocksize )
if not data :
break
safe_send ( data )
total + = len ( data )
meter . update ( total )
# Cleanup
stream . finish ( )
meter . end ( size )
except :
if vol :
vol . delete ( 0 )
raise
return vol
2013-07-16 18:12:13 -04:00
def _perform_initrd_injections ( initrd , injections , scratchdir ) :
"""
Insert files into the root directory of the initial ram disk
"""
if not injections :
return
tempdir = tempfile . mkdtemp ( dir = scratchdir )
os . chmod ( tempdir , 0775 )
for filename in injections :
logging . debug ( " Copying %s to the initrd. " , filename )
shutil . copy ( filename , tempdir )
logging . debug ( " Appending to the initrd. " )
find_proc = subprocess . Popen ( [ ' find ' , ' . ' , ' -print0 ' ] ,
stdout = subprocess . PIPE ,
stderr = subprocess . PIPE ,
cwd = tempdir )
cpio_proc = subprocess . Popen ( [ ' cpio ' , ' -o ' , ' --null ' , ' -Hnewc ' , ' --quiet ' ] ,
stdin = find_proc . stdout ,
stdout = subprocess . PIPE ,
stderr = subprocess . PIPE ,
cwd = tempdir )
f = open ( initrd , ' ab ' )
gzip_proc = subprocess . Popen ( [ ' gzip ' ] , stdin = cpio_proc . stdout ,
stdout = f , stderr = subprocess . PIPE )
cpio_proc . wait ( )
find_proc . wait ( )
gzip_proc . wait ( )
f . close ( )
shutil . rmtree ( tempdir )
finderr = find_proc . stderr . read ( )
cpioerr = cpio_proc . stderr . read ( )
gziperr = gzip_proc . stderr . read ( )
if finderr :
logging . debug ( " find stderr= %s " , finderr )
if cpioerr :
logging . debug ( " cpio stderr= %s " , cpioerr )
if gziperr :
logging . debug ( " gzip stderr= %s " , gziperr )
def _support_remote_url_install ( conn ) :
if hasattr ( conn , " _virtinst__fake_conn " ) :
return False
return conn . check_stream_support ( conn . SUPPORT_STREAM_UPLOAD )
def _upload_media ( conn , scratchdir , system_scratchdir ,
meter , kernel , initrd ) :
"""
Upload kernel / initrd media to remote connection if necessary
"""
tmpvols = [ ]
2013-04-12 09:51:26 -04:00
2013-07-16 18:12:13 -04:00
if ( not conn . is_remote ( ) and
( conn . is_session_uri ( ) or scratchdir == system_scratchdir ) ) :
# We have access to system scratchdir, don't jump through hoops
logging . debug ( " Have access to preferred scratchdir so "
" nothing to upload " )
return kernel , initrd , tmpvols
2013-03-17 17:06:52 -04:00
2013-07-16 18:12:13 -04:00
if not _support_remote_url_install ( conn ) :
logging . debug ( " Media upload not supported " )
return kernel , initrd , tmpvols
2013-03-17 17:06:52 -04:00
2013-07-16 18:12:13 -04:00
# Build pool
logging . debug ( " Uploading kernel/initrd media " )
pool = _build_pool ( conn , meter , system_scratchdir )
2013-03-17 17:06:52 -04:00
2013-07-16 18:12:13 -04:00
kvol = _upload_file ( conn , meter , pool , kernel )
newkernel = kvol . path ( )
tmpvols . append ( kvol )
2013-03-17 17:06:52 -04:00
2013-07-16 18:12:13 -04:00
ivol = _upload_file ( conn , meter , pool , initrd )
newinitrd = ivol . path ( )
tmpvols . append ( ivol )
2013-03-17 17:06:52 -04:00
2013-07-16 18:12:13 -04:00
return newkernel , newinitrd , tmpvols
class DistroInstaller ( Installer . Installer ) :
def __init__ ( self , * args , * * kwargs ) :
Installer . Installer . __init__ ( self , * args , * * kwargs )
self . livecd = False
2013-03-17 17:06:52 -04:00
self . _location_is_path = True
2013-07-16 18:12:13 -04:00
#######################
# Install prepartions #
#######################
2013-03-17 17:06:52 -04:00
def _prepare_cdrom ( self , guest , meter ) :
transient = not self . livecd
if not self . _location_is_path :
# Xen needs a boot.iso if its a http://, ftp://, or nfs: url
2013-04-12 08:26:21 -04:00
( store_ignore , os_type_ignore ,
os_variant_ignore , media ) = OSDistro . getBootDisk ( guest ,
self . location ,
meter ,
self . scratchdir )
2013-03-17 17:06:52 -04:00
cdrom = media
self . _tmpfiles . append ( cdrom )
transient = True
else :
cdrom = self . location
2013-07-13 10:09:00 -04:00
disk = self . _make_cdrom_dev ( cdrom )
disk . transient = transient
2013-03-17 17:06:52 -04:00
self . install_devices . append ( disk )
def _prepare_kernel_and_initrd ( self , guest , meter ) :
disk = None
2013-07-13 10:09:00 -04:00
# If installing off a local path, map it through to a virtual CD
2013-03-17 17:06:52 -04:00
if ( self . location is not None and
self . _location_is_path and
not os . path . isdir ( self . location ) ) :
2013-07-13 10:09:00 -04:00
disk = self . _make_cdrom_dev ( self . location )
disk . transient = True
2013-03-17 17:06:52 -04:00
# Make sure we always fetch kernel here if required
if self . _install_bootconfig . kernel and not self . scratchdir_required ( ) :
return disk
# Need to fetch the kernel & initrd from a remote site, or
# out of a loopback mounted disk image/device
2013-04-12 08:26:21 -04:00
ignore , os_type , os_variant , media = OSDistro . getKernel ( guest ,
2013-03-17 17:06:52 -04:00
self . location , meter ,
self . scratchdir ,
self . os_type )
( kernelfn , initrdfn , args ) = media
if guest . get_os_autodetect ( ) :
if os_type :
logging . debug ( " Auto detected OS type as: %s " , os_type )
guest . os_type = os_type
if ( os_variant and guest . os_type == os_type ) :
logging . debug ( " Auto detected OS variant as: %s " , os_variant )
guest . os_variant = os_variant
self . _tmpfiles . append ( kernelfn )
if initrdfn :
self . _tmpfiles . append ( initrdfn )
2013-07-16 18:12:13 -04:00
_perform_initrd_injections ( initrdfn ,
self . _initrd_injections ,
self . scratchdir )
2013-03-17 17:06:52 -04:00
2013-07-16 18:12:13 -04:00
kernelfn , initrdfn , tmpvols = _upload_media (
guest . conn , self . scratchdir , self . _get_system_scratchdir ( ) ,
meter , kernelfn , initrdfn )
self . _tmpvols + = tmpvols
2013-03-17 17:06:52 -04:00
self . _install_bootconfig . kernel = kernelfn
self . _install_bootconfig . initrd = initrdfn
self . _install_bootconfig . kernel_args = args
return disk
2013-07-16 18:12:13 -04:00
###########################
# Private installer impls #
###########################
2013-03-17 17:06:52 -04:00
def _get_bootdev ( self , isinstall , guest ) :
2013-07-16 18:12:13 -04:00
persistent_cd = ( self . _location_is_path and
self . cdrom and
self . livecd )
if isinstall or persistent_cd :
2013-03-17 17:06:52 -04:00
bootdev = self . bootconfig . BOOT_DEVICE_CDROM
else :
bootdev = self . bootconfig . BOOT_DEVICE_HARDDISK
return bootdev
2013-07-16 18:12:13 -04:00
def _validate_location ( self , val ) :
"""
Valid values for location :
1 ) it can be a local file ( ex . boot . iso ) , directory ( ex . distro
tree ) or physical device ( ex . cdrom media )
2 ) http , ftp , or nfs path for an install tree
"""
is_local = not self . conn . is_remote ( )
if _is_url ( val , is_local ) :
self . _location_is_path = False
self . _location = _sanitize_url ( val )
logging . debug ( " DistroInstaller location is a network source. " )
return val
try :
d = self . _make_cdrom_dev ( val )
val = d . path
except :
logging . debug ( " Error validating install location " , exc_info = True )
raise ValueError ( _ ( " Checking installer location failed: "
" Could not find media ' %s ' . " % str ( val ) ) )
self . _location_is_path = True
return val
##########################
# Public installer impls #
##########################
2013-03-17 17:06:52 -04:00
def scratchdir_required ( self ) :
if not self . location :
return False
is_url = not self . _location_is_path
mount_dvd = self . _location_is_path and not self . cdrom
return bool ( is_url or mount_dvd )
2013-07-16 18:12:13 -04:00
def _prepare ( self , guest , meter ) :
2013-03-17 17:06:52 -04:00
dev = None
if self . cdrom :
if self . location :
dev = self . _prepare_cdrom ( guest , meter )
else :
# Booting from a cdrom directly allocated to the guest
pass
else :
dev = self . _prepare_kernel_and_initrd ( guest , meter )
if dev :
self . install_devices . append ( dev )
def check_location ( self ) :
if self . _location_is_path :
# We already mostly validated this
return True
2013-07-16 18:12:13 -04:00
# This will throw an error for us
OSDistro . detectMediaDistro ( location = self . location , arch = self . arch )
2013-03-17 17:06:52 -04:00
return True
def detect_distro ( self ) :
try :
dist_info = OSDistro . detectMediaDistro ( location = self . location ,
arch = self . arch )
except :
logging . exception ( " Error attempting to detect distro. " )
return ( None , None )
# detectMediaDistro should only return valid values
dtype , dvariant = dist_info
return ( dtype , dvariant )