2013-03-18 01:06:52 +04:00
#
2014-03-26 13:30:11 +04:00
# Copyright 2006-2009, 2013, 2014 Red Hat, Inc.
2013-03-18 01:06:52 +04:00
# 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
2013-10-28 00:59:47 +04:00
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
2013-03-18 01:06:52 +04:00
#
# 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-09-20 04:18:12 +04:00
from virtinst import StoragePool , StorageVolume
2013-04-11 03:48:07 +04:00
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
2014-03-26 13:30:11 +04:00
from virtinst import osdict
2013-03-18 01:06:52 +04:00
2014-01-12 01:10:38 +04:00
def _is_url ( conn , url ) :
2013-03-18 01:06:52 +04:00
"""
Check if passed string is a ( pseudo ) valid http , ftp , or nfs url .
"""
2014-01-12 01:10:38 +04:00
if not conn . is_remote ( ) and os . path . exists ( url ) :
2013-03-18 01:06:52 +04:00
if os . path . isdir ( url ) :
return True
else :
return False
2014-09-06 23:08:40 +04:00
return ( url . startswith ( " http:// " ) or url . startswith ( " https:// " ) or
url . startswith ( " ftp:// " ) or url . startswith ( " nfs: " ) )
2013-03-18 01:06:52 +04:00
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-09-23 01:34:53 +04:00
pool = StoragePool . 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-09-20 04:18:12 +04:00
poolbuild = StoragePool ( conn )
poolbuild . type = poolbuild . TYPE_DIR
poolbuild . name = name
poolbuild . target_path = path
2013-03-18 01:06:52 +04:00
# 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 )
2014-01-18 23:57:39 +04:00
conn . clear_cache ( pools = True )
2013-07-26 00:21:30 +04:00
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-09-23 01:34:53 +04:00
xmlobj = StoragePool ( conn , parsexml = destpool . XMLDesc ( 0 ) )
poolpath = xmlobj . target_path
2013-10-01 00:21:23 +04:00
name = StorageVolume . find_free_name ( destpool , basename )
2013-03-18 01:06:52 +04:00
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
2014-01-28 18:52:39 +04:00
vol . upload ( stream , offset , length , flags )
2013-03-18 01:06:52 +04:00
# 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-09-03 00:10:28 +04:00
def _rhel4_initrd_inject ( initrd , injections ) :
try :
file_proc = subprocess . Popen ( [ " file " , " -z " , initrd ] ,
stdout = subprocess . PIPE ,
stderr = subprocess . PIPE )
2014-05-02 18:20:59 +04:00
if " ext2 filesystem " not in file_proc . communicate ( ) [ 0 ] :
2013-09-03 00:10:28 +04:00
return False
except :
logging . exception ( " Failed to file command for rhel4 initrd detection " )
return False
logging . debug ( " Is RHEL4 initrd " )
# Uncompress the initrd
newinitrd = file ( initrd + " .new " , " wb " )
gzip_proc = subprocess . Popen ( [ " gzip " , " -d " , " -f " , " -c " , initrd ] ,
stdout = newinitrd ,
stderr = subprocess . PIPE )
gzip_proc . wait ( )
newinitrd . close ( )
debugfserr = " "
for filename in injections :
# We have an ext2 filesystem, use debugfs to inject files
cmd = [ " debugfs " , " -w " , " -R " ,
" write %s %s " % ( filename , os . path . basename ( filename ) ) ,
newinitrd . name ]
logging . debug ( " Copying %s to the initrd with cmd= %s " , filename , cmd )
debugfs_proc = subprocess . Popen ( cmd ,
stdout = subprocess . PIPE ,
stderr = subprocess . PIPE )
debugfs_proc . wait ( )
debugfserr + = debugfs_proc . stderr . read ( ) or " "
gziperr = gzip_proc . stderr . read ( )
if gziperr :
logging . debug ( " gzip stderr= %s " , gziperr )
if debugfserr :
logging . debug ( " debugfs stderr= %s " , debugfserr )
# Recompress the initrd
gzip_proc = subprocess . Popen ( [ " gzip " ] ,
stdin = file ( newinitrd . name , " rb " ) ,
stdout = file ( initrd , " wb " ) ,
stderr = subprocess . PIPE )
gzip_proc . wait ( )
gziperr = gzip_proc . stderr . read ( )
if gziperr :
logging . debug ( " gzip stderr= %s " , gziperr )
os . unlink ( newinitrd . name )
return True
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
2013-09-03 00:10:28 +04:00
if _rhel4_initrd_inject ( initrd , injections ) :
return
2013-07-17 02:12:13 +04:00
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-10-06 18:08:04 +04:00
if not conn . support_remote_url_install ( ) :
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
2014-01-12 01:10:38 +04:00
# Enum of the various install media types we can have
2014-03-06 21:35:01 +04:00
( MEDIA_LOCATION_DIR ,
MEDIA_LOCATION_CDROM ,
2014-01-12 01:10:38 +04:00
MEDIA_LOCATION_URL ,
MEDIA_CDROM_PATH ,
MEDIA_CDROM_URL ,
2014-03-06 21:35:01 +04:00
MEDIA_CDROM_IMPLIED ) = range ( 1 , 7 )
2014-01-12 01:10:38 +04:00
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
2013-07-17 02:12:13 +04:00
#######################
# Install prepartions #
#######################
2013-03-18 01:06:52 +04:00
2014-01-12 01:10:38 +04:00
def _get_media_type ( self ) :
if self . cdrom and not self . location :
# CDROM install requested from a disk already attached to VM
return MEDIA_CDROM_IMPLIED
if self . location and _is_url ( self . conn , self . location ) :
return self . cdrom and MEDIA_CDROM_URL or MEDIA_LOCATION_URL
2014-03-06 21:35:01 +04:00
if self . cdrom :
return MEDIA_CDROM_PATH
if self . location and os . path . isdir ( self . location ) :
return MEDIA_LOCATION_DIR
return MEDIA_LOCATION_CDROM
2014-01-12 01:10:38 +04:00
2013-09-26 21:04:28 +04:00
def _prepare_local ( self ) :
transient = True
if self . cdrom :
transient = not self . livecd
return self . _make_cdrom_dev ( self . location , transient = transient )
2013-03-18 01:06:52 +04:00
2013-09-26 21:04:28 +04:00
def _prepare_cdrom_url ( self , guest , fetcher ) :
store = urlfetcher . getDistroStore ( guest , fetcher )
media = store . acquireBootDisk ( guest )
self . _tmpfiles . append ( media )
return self . _make_cdrom_dev ( media , transient = True )
2013-03-18 01:06:52 +04:00
2013-09-26 21:04:28 +04:00
def _prepare_kernel_url ( self , guest , fetcher ) :
store = urlfetcher . getDistroStore ( guest , fetcher )
kernel , initrd , args = store . acquireKernel ( guest )
self . _tmpfiles . append ( kernel )
if initrd :
self . _tmpfiles . append ( initrd )
2013-03-18 01:06:52 +04:00
2013-09-26 21:04:28 +04:00
_perform_initrd_injections ( initrd ,
2013-07-17 04:39:24 +04:00
self . initrd_injections ,
2013-09-26 21:04:28 +04:00
fetcher . scratchdir )
2013-03-18 01:06:52 +04:00
2013-09-26 21:04:28 +04:00
kernel , initrd , tmpvols = _upload_media (
guest . conn , fetcher . scratchdir ,
2013-07-17 15:53:47 +04:00
util . get_system_scratchdir ( guest . type ) ,
2013-09-26 21:04:28 +04:00
fetcher . meter , kernel , initrd )
2013-07-17 02:12:13 +04:00
self . _tmpvols + = tmpvols
2013-03-18 01:06:52 +04:00
2013-09-26 21:04:28 +04:00
self . _install_kernel = kernel
self . _install_initrd = initrd
2014-01-12 00:28:21 +04:00
self . extraargs = args
2013-03-18 01:06:52 +04:00
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 ) :
2014-01-12 01:10:38 +04:00
mediatype = self . _get_media_type ( )
local = mediatype in [ MEDIA_CDROM_PATH , MEDIA_CDROM_IMPLIED ,
2014-03-06 21:35:01 +04:00
MEDIA_LOCATION_DIR , MEDIA_LOCATION_CDROM ]
2014-01-12 01:10:38 +04:00
persistent_cd = ( local and
2013-07-17 02:12:13 +04:00
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
"""
2014-01-12 01:10:38 +04:00
if _is_url ( self . conn , val ) :
2013-07-17 02:12:13 +04:00
logging . debug ( " DistroInstaller location is a network source. " )
2013-09-24 05:23:36 +04:00
return _sanitize_url ( val )
2013-07-17 02:12:13 +04:00
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 ) ) )
return val
2014-09-07 21:35:34 +04:00
def _prepare ( self , guest , meter ) :
2014-01-12 01:10:38 +04:00
mediatype = self . _get_media_type ( )
2014-09-07 21:35:34 +04:00
scratchdir = util . make_scratchdir ( guest . conn , guest . type )
logging . debug ( " Using scratchdir= %s " , scratchdir )
2013-09-26 21:04:28 +04:00
# Test suite manually injected a boot kernel
if self . _install_kernel and not self . scratchdir_required ( ) :
return
2014-01-12 01:10:38 +04:00
if mediatype == MEDIA_CDROM_IMPLIED :
return
2013-09-26 21:04:28 +04:00
dev = None
2014-03-06 21:35:01 +04:00
if mediatype == MEDIA_CDROM_PATH or mediatype == MEDIA_LOCATION_CDROM :
2013-09-26 21:04:28 +04:00
dev = self . _prepare_local ( )
2014-03-06 21:35:01 +04:00
if mediatype != MEDIA_CDROM_PATH :
2014-09-07 21:35:34 +04:00
fetcher = urlfetcher . fetcherForURI ( self . location , scratchdir ,
meter )
2013-09-26 21:04:28 +04:00
try :
try :
fetcher . prepareLocation ( )
except ValueError , e :
logging . exception ( " Error preparing install location " )
raise ValueError ( _ ( " Invalid install location: " ) + str ( e ) )
2014-01-12 01:10:38 +04:00
if mediatype == MEDIA_CDROM_URL :
2013-09-26 21:04:28 +04:00
dev = self . _prepare_cdrom_url ( guest , fetcher )
else :
self . _prepare_kernel_url ( guest , fetcher )
finally :
fetcher . cleanupLocation ( )
if dev :
self . install_devices . append ( dev )
2013-07-17 02:12:13 +04:00
##########################
# Public installer impls #
##########################
2013-03-18 01:06:52 +04:00
def scratchdir_required ( self ) :
if not self . location :
return False
2014-01-12 01:10:38 +04:00
mediatype = self . _get_media_type ( )
return mediatype in [ MEDIA_CDROM_URL , MEDIA_LOCATION_URL ,
2014-03-06 21:35:01 +04:00
MEDIA_LOCATION_DIR , MEDIA_LOCATION_CDROM ]
2013-03-18 01:06:52 +04:00
2013-09-26 21:04:28 +04:00
def check_location ( self , guest ) :
2014-01-12 01:10:38 +04:00
mediatype = self . _get_media_type ( )
if mediatype not in [ MEDIA_CDROM_URL , MEDIA_LOCATION_URL ] :
2013-03-18 01:06:52 +04:00
return True
2013-07-17 02:12:13 +04:00
# This will throw an error for us
2013-09-26 21:04:28 +04:00
urlfetcher . detectMediaDistro ( guest , self . location )
2013-03-18 01:06:52 +04:00
return True
2013-09-26 21:04:28 +04:00
def detect_distro ( self , guest ) :
2014-09-07 19:57:04 +04:00
distro = None
2014-03-26 13:30:11 +04:00
try :
2014-09-07 19:57:04 +04:00
if _is_url ( self . conn , self . location ) :
distro = urlfetcher . detectMediaDistro ( guest , self . location )
else :
distro = osdict . lookup_os_by_media ( self . location )
2014-03-26 13:30:11 +04:00
except :
2014-09-07 19:57:04 +04:00
logging . debug ( " Error attempting to detect distro. " , exc_info = True )
2014-03-26 13:30:11 +04:00
2014-09-07 19:57:04 +04:00
logging . debug ( " installer.detect_distro returned= %s " , distro )
return distro