2013-03-18 01: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-07-17 15:53:47 +04:00
from virtinst import support
2013-04-11 03:48:07 +04:00
from virtinst import Storage
from virtinst import util
from virtinst import Installer
2013-08-09 04:47:17 +04:00
from virtinst import VirtualDisk
2013-08-09 05:42:44 +04:00
from virtinst import urlfetcher
2013-03-18 01: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 22:34:52 +04:00
2013-03-18 01: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 22:34:52 +04:00
2013-03-18 01:06:52 +04:00
def _build_pool ( conn , meter , path ) :
2013-04-11 18:27:02 +04:00
pool = util . lookup_pool_by_path ( conn , path )
2013-03-18 01: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 18:27:02 +04:00
name = util . generate_name ( " boot-scratch " ,
2013-03-18 01:06:52 +04:00
conn . storagePoolLookupByName )
logging . debug ( " Building storage pool: path= %s name= %s " , path , name )
2013-07-06 04:14:57 +04:00
poolbuild = Storage . DirectoryPool ( conn , name = name ,
2013-03-18 01: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
2013-07-26 00:21:30 +04:00
ret = poolbuild . install ( meter = meter , create = True , build = False ,
autostart = True )
conn . clear_cache ( )
return ret
2013-03-18 01:06:52 +04:00
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 18:53:30 +04:00
poolpath = util . xpath ( destpool . XMLDesc ( 0 ) , " /pool/target/path " )
2013-03-18 01: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-26 00:21:30 +04:00
vol_install = VirtualDisk . build_vol_install ( conn , name , destpool ,
( float ( size ) / 1024.0 / 1024.0 / 1024.0 ) , True )
2013-07-13 18:09:00 +04:00
disk = VirtualDisk ( conn )
disk . path = os . path . join ( poolpath , name )
2013-07-26 00:21:30 +04:00
disk . set_create_storage ( vol_install = vol_install )
2013-07-13 18:09:00 +04:00
disk . validate ( )
2013-03-18 01:06:52 +04:00
2013-07-06 04:14:57 +04:00
disk . setup ( meter = meter )
2013-07-13 18:09:00 +04:00
vol = disk . get_vol_object ( )
2013-03-18 01: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 22:34:52 +04:00
# blocksize = (1024 ** 2)
2013-03-18 01: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-17 02: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 _upload_media ( conn , scratchdir , system_scratchdir ,
meter , kernel , initrd ) :
"""
Upload kernel / initrd media to remote connection if necessary
"""
tmpvols = [ ]
2013-04-12 17:51:26 +04:00
2013-07-17 02: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-18 01:06:52 +04:00
2013-07-17 15:53:47 +04:00
if not support . support_remote_url_install ( conn ) :
2013-07-17 02:12:13 +04:00
logging . debug ( " Media upload not supported " )
return kernel , initrd , tmpvols
2013-03-18 01:06:52 +04:00
2013-07-17 02:12:13 +04:00
# Build pool
logging . debug ( " Uploading kernel/initrd media " )
pool = _build_pool ( conn , meter , system_scratchdir )
2013-03-18 01:06:52 +04:00
2013-07-17 02:12:13 +04:00
kvol = _upload_file ( conn , meter , pool , kernel )
newkernel = kvol . path ( )
tmpvols . append ( kvol )
2013-03-18 01:06:52 +04:00
2013-07-17 02:12:13 +04:00
ivol = _upload_file ( conn , meter , pool , initrd )
newinitrd = ivol . path ( )
tmpvols . append ( ivol )
2013-03-18 01:06:52 +04:00
2013-07-17 02:12:13 +04:00
return newkernel , newinitrd , tmpvols
2013-08-09 04:47:17 +04:00
class DistroInstaller ( Installer ) :
2013-07-17 02:12:13 +04:00
def __init__ ( self , * args , * * kwargs ) :
2013-08-09 04:47:17 +04:00
Installer . __init__ ( self , * args , * * kwargs )
2013-07-17 02:12:13 +04:00
self . livecd = False
2013-03-18 01:06:52 +04:00
self . _location_is_path = True
2013-07-17 02:12:13 +04:00
#######################
# Install prepartions #
#######################
2013-03-18 01:06:52 +04:00
2013-07-17 15:53:47 +04:00
def _prepare_cdrom ( self , guest , meter , scratchdir ) :
2013-03-18 01:06:52 +04:00
transient = not self . livecd
if not self . _location_is_path :
2013-08-11 02:48:43 +04:00
( store_ignore ,
2013-08-09 05:42:44 +04:00
os_variant_ignore , media ) = urlfetcher . getBootDisk ( guest ,
2013-04-12 16:26:21 +04:00
self . location ,
meter ,
2013-07-17 15:53:47 +04:00
scratchdir )
2013-03-18 01:06:52 +04:00
cdrom = media
self . _tmpfiles . append ( cdrom )
transient = True
else :
cdrom = self . location
2013-07-13 18:09:00 +04:00
disk = self . _make_cdrom_dev ( cdrom )
disk . transient = transient
2013-03-18 01:06:52 +04:00
self . install_devices . append ( disk )
2013-07-17 15:53:47 +04:00
def _prepare_kernel_and_initrd ( self , guest , meter , scratchdir ) :
2013-03-18 01:06:52 +04:00
disk = None
2013-07-13 18:09:00 +04:00
# If installing off a local path, map it through to a virtual CD
2013-03-18 01: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 18:09:00 +04:00
disk = self . _make_cdrom_dev ( self . location )
disk . transient = True
2013-03-18 01:06:52 +04:00
2013-07-17 15:53:47 +04:00
# Don't fetch kernel if test suite manually injected a boot kernel
2013-07-17 04:05:24 +04:00
if self . _install_kernel and not self . scratchdir_required ( ) :
2013-03-18 01:06:52 +04:00
return disk
2013-08-11 02:48:43 +04:00
ignore , os_variant , media = urlfetcher . getKernel ( guest ,
2013-03-18 01:06:52 +04:00
self . location , meter ,
2013-07-17 15:53:47 +04:00
scratchdir ,
guest . os . os_type )
2013-03-18 01:06:52 +04:00
( kernelfn , initrdfn , args ) = media
2013-07-18 01:58:24 +04:00
if guest . os_autodetect :
2013-08-11 02:48:43 +04:00
if os_variant :
2013-03-18 01:06:52 +04:00
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-17 02:12:13 +04:00
_perform_initrd_injections ( initrdfn ,
2013-07-17 04:39:24 +04:00
self . initrd_injections ,
2013-07-17 15:53:47 +04:00
scratchdir )
2013-03-18 01:06:52 +04:00
2013-07-17 02:12:13 +04:00
kernelfn , initrdfn , tmpvols = _upload_media (
2013-07-17 15:53:47 +04:00
guest . conn , scratchdir ,
util . get_system_scratchdir ( guest . type ) ,
2013-07-17 02:12:13 +04:00
meter , kernelfn , initrdfn )
self . _tmpvols + = tmpvols
2013-03-18 01:06:52 +04:00
2013-07-17 04:05:24 +04:00
self . _install_kernel = kernelfn
self . _install_initrd = initrdfn
self . _install_args = args
2013-03-18 01:06:52 +04:00
return disk
2013-07-17 02:12:13 +04:00
###########################
# Private installer impls #
###########################
2013-03-18 01:06:52 +04:00
def _get_bootdev ( self , isinstall , guest ) :
2013-07-17 02:12:13 +04:00
persistent_cd = ( self . _location_is_path and
self . cdrom and
self . livecd )
if isinstall or persistent_cd :
2013-07-17 15:53:47 +04:00
bootdev = " cdrom "
2013-03-18 01:06:52 +04:00
else :
2013-07-17 15:53:47 +04:00
bootdev = " hd "
2013-03-18 01:06:52 +04:00
return bootdev
2013-07-17 02: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-18 01: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-17 15:53:47 +04:00
def _prepare ( self , guest , meter , scratchdir ) :
logging . debug ( " Using scratchdir= %s " , scratchdir )
2013-03-18 01:06:52 +04:00
dev = None
if self . cdrom :
if self . location :
2013-07-17 15:53:47 +04:00
dev = self . _prepare_cdrom ( guest , meter , scratchdir )
2013-03-18 01:06:52 +04:00
else :
# Booting from a cdrom directly allocated to the guest
pass
else :
2013-07-17 15:53:47 +04:00
dev = self . _prepare_kernel_and_initrd ( guest , meter , scratchdir )
2013-03-18 01:06:52 +04:00
if dev :
self . install_devices . append ( dev )
2013-07-17 15:53:47 +04:00
def check_location ( self , arch ) :
2013-03-18 01:06:52 +04:00
if self . _location_is_path :
# We already mostly validated this
return True
2013-07-17 02:12:13 +04:00
# This will throw an error for us
2013-08-09 05:42:44 +04:00
urlfetcher . detectMediaDistro ( self . location , arch )
2013-03-18 01:06:52 +04:00
return True
2013-07-17 15:53:47 +04:00
def detect_distro ( self , arch ) :
2013-03-18 01:06:52 +04:00
try :
2013-08-11 02:48:43 +04:00
return urlfetcher . detectMediaDistro ( self . location , arch )
2013-03-18 01:06:52 +04:00
except :
logging . exception ( " Error attempting to detect distro. " )
2013-08-11 02:48:43 +04:00
return None