2013-07-13 18:09:00 +04:00
#
# Storage lookup/creation helpers
#
2013-10-28 00:59:47 +04:00
# Copyright 2013 Red Hat, Inc.
2013-07-13 18:09:00 +04:00
#
# 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-07-13 18:09:00 +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 statvfs
import libvirt
2013-09-23 01:34:53 +04:00
from virtinst import StoragePool , StorageVolume
2013-07-13 18:09:00 +04:00
from virtinst import util
def _check_if_pool_source ( conn , path ) :
"""
If passed path is a host disk device like / dev / sda , want to let the user
use it
"""
2013-10-06 18:08:04 +04:00
if not conn . check_support ( conn . SUPPORT_CONN_STORAGE ) :
2013-07-13 18:09:00 +04:00
return None
def check_pool ( poolname , path ) :
pool = conn . storagePoolLookupByName ( poolname )
2013-09-23 01:34:53 +04:00
xmlobj = StoragePool ( conn , parsexml = pool . XMLDesc ( 0 ) )
if xmlobj . source_path == path :
return pool
2013-07-13 18:09:00 +04:00
running_list = conn . listStoragePools ( )
inactive_list = conn . listDefinedStoragePools ( )
for plist in [ running_list , inactive_list ] :
for name in plist :
p = check_pool ( name , path )
if p :
return p
return None
def check_if_path_managed ( conn , path ) :
"""
Determine if we can use libvirt storage APIs to create or lookup
the passed path . If we can ' t, throw an error
"""
vol = None
pool = None
verr = None
path_is_pool = False
def lookup_vol_by_path ( ) :
try :
vol = conn . storageVolLookupByPath ( path )
vol . info ( )
return vol , None
except libvirt . libvirtError , e :
if ( hasattr ( libvirt , " VIR_ERR_NO_STORAGE_VOL " )
and e . get_error_code ( ) != libvirt . VIR_ERR_NO_STORAGE_VOL ) :
raise
return None , e
def lookup_vol_name ( name ) :
try :
name = os . path . basename ( path )
if pool and name in pool . listVolumes ( ) :
return pool . lookupByName ( name )
except :
pass
return None
vol = lookup_vol_by_path ( ) [ 0 ]
if not vol :
2013-09-23 01:34:53 +04:00
pool = StoragePool . lookup_pool_by_path ( conn , os . path . dirname ( path ) )
2013-07-13 18:09:00 +04:00
# Is pool running?
if pool and pool . info ( ) [ 0 ] != libvirt . VIR_STORAGE_POOL_RUNNING :
pool = None
# Attempt to lookup path as a storage volume
if pool and not vol :
try :
# Pool may need to be refreshed, but if it errors,
# invalidate it
pool . refresh ( 0 )
vol , verr = lookup_vol_by_path ( )
if verr :
vol = lookup_vol_name ( os . path . basename ( path ) )
except Exception , e :
vol = None
pool = None
verr = str ( e )
if not vol :
# See if path is a pool source, and allow it through
trypool = _check_if_pool_source ( conn , path )
if trypool :
path_is_pool = True
pool = trypool
if not vol and not pool :
if not conn . is_remote ( ) :
# Building local disk
return None , None , False
if not verr :
# Since there is no error, no pool was ever found
err = ( _ ( " Cannot use storage ' %(path)s ' : ' %(rootdir)s ' is "
" not managed on the remote host. " ) %
{ ' path ' : path ,
' rootdir ' : os . path . dirname ( path ) } )
else :
err = ( _ ( " Cannot use storage %(path)s : %(err)s " ) %
{ ' path ' : path , ' err ' : verr } )
raise ValueError ( err )
return vol , pool , path_is_pool
2013-07-26 00:21:30 +04:00
def build_vol_install ( conn , path , pool , size , sparse ) :
2013-07-13 18:09:00 +04:00
# Path wasn't a volume. See if base of path is a managed
# pool, and if so, setup a StorageVolume object
if size is None :
raise ValueError ( _ ( " Size must be specified for non "
" existent volume path ' %s ' " % path ) )
logging . debug ( " Path ' %s ' is target for pool ' %s ' . "
" Creating volume ' %s ' . " ,
os . path . dirname ( path ) , pool . name ( ) ,
os . path . basename ( path ) )
cap = ( size * 1024 * 1024 * 1024 )
if sparse :
alloc = 0
else :
alloc = cap
2013-09-20 04:18:12 +04:00
volinst = StorageVolume ( conn )
volinst . pool = pool
volinst . name = os . path . basename ( path )
volinst . capacity = cap
volinst . allocation = alloc
2013-07-13 18:09:00 +04:00
return volinst
class _StorageBase ( object ) :
def get_size ( self ) :
raise NotImplementedError ( )
def get_dev_type ( self ) :
raise NotImplementedError ( )
def is_managed ( self ) :
raise NotImplementedError ( )
def get_driver_type ( self ) :
raise NotImplementedError ( )
class StorageCreator ( _StorageBase ) :
def __init__ ( self , conn , path , pool ,
2013-09-29 19:23:59 +04:00
vol_install , clone_path , backing_store ,
2013-07-13 18:09:00 +04:00
size , sparse , fmt ) :
_StorageBase . __init__ ( self )
self . _conn = conn
self . _pool = pool
self . _vol_install = vol_install
self . _path = path
self . _size = size
self . _sparse = sparse
self . _clone_path = clone_path
self . fake = False
if not self . _vol_install and self . _pool :
2013-07-26 00:21:30 +04:00
self . _vol_install = build_vol_install ( conn , path , pool ,
2013-07-13 18:09:00 +04:00
size , sparse )
self . _set_format ( fmt )
2013-09-29 19:23:59 +04:00
self . _set_backing_store ( backing_store )
2013-07-13 18:09:00 +04:00
if self . _vol_install :
self . _path = None
self . _size = None
# Cached bits
self . _dev_type = None
###############
# Private API #
###############
def _set_format ( self , val ) :
if val is None :
return
if self . _vol_install :
2013-09-20 04:18:12 +04:00
if not self . _vol_install . supports_property ( " format " ) :
2013-07-13 18:09:00 +04:00
raise ValueError ( _ ( " Storage type does not support format "
" parameter. " ) )
2013-09-20 04:18:12 +04:00
if self . _vol_install . format != val :
self . _vol_install . format = val
2013-07-13 18:09:00 +04:00
elif val != " raw " :
raise RuntimeError ( _ ( " Format cannot be specified for "
" unmanaged storage. " ) )
2013-09-29 19:23:59 +04:00
def _set_backing_store ( self , val ) :
if val is None :
return
if not self . _vol_install :
raise RuntimeError ( _ ( " Cannot set backing store for unmanaged "
" storage. " ) )
self . _vol_install . backing_store = val
2013-07-13 18:09:00 +04:00
##############
# Public API #
##############
def _get_path ( self ) :
if self . _vol_install and not self . _path :
2013-09-23 01:34:53 +04:00
xmlobj = StoragePool ( self . _conn ,
parsexml = self . _vol_install . pool . XMLDesc ( 0 ) )
self . _path = ( xmlobj . target_path + " / " + self . _vol_install . name )
2013-07-13 18:09:00 +04:00
return self . _path
path = property ( _get_path )
def get_vol_install ( self ) :
return self . _vol_install
def get_sparse ( self ) :
return self . _sparse
def get_size ( self ) :
if not self . _size :
self . _size = ( float ( self . _vol_install . capacity ) /
1024.0 / 1024.0 / 1024.0 )
return self . _size
def get_dev_type ( self ) :
if not self . _dev_type :
if self . _vol_install :
if self . _vol_install . file_type == libvirt . VIR_STORAGE_VOL_FILE :
self . _dev_type = " file "
else :
self . _dev_type = " block "
else :
self . _dev_type = " file "
return self . _dev_type
def get_driver_type ( self ) :
if self . _vol_install :
2013-09-20 04:18:12 +04:00
if self . _vol_install . supports_property ( " format " ) :
2013-07-13 18:09:00 +04:00
return self . _vol_install . format
return " raw "
def is_managed ( self ) :
return bool ( self . _vol_install )
def validate ( self , device , devtype ) :
if device in [ " floppy " , " cdrom " ] :
raise ValueError ( _ ( " Cannot create storage for %s device. " ) %
device )
if self . is_managed ( ) :
2013-09-20 04:18:12 +04:00
return self . _vol_install . validate ( )
2013-07-13 18:09:00 +04:00
if devtype == " block " :
raise ValueError ( _ ( " Local block device path ' %s ' must "
" exist. " ) % self . path )
if not os . access ( os . path . dirname ( self . path ) , os . R_OK ) :
raise ValueError ( " No read access to directory ' %s ' " %
os . path . dirname ( self . path ) )
if self . _size is None :
raise ValueError ( _ ( " size is required for non-existent disk "
" ' %s ' " % self . path ) )
if not os . access ( os . path . dirname ( self . path ) , os . W_OK ) :
raise ValueError ( _ ( " No write access to directory ' %s ' " ) %
os . path . dirname ( self . path ) )
def is_size_conflict ( self ) :
if self . _vol_install :
return self . _vol_install . is_size_conflict ( )
ret = False
msg = None
vfs = os . statvfs ( os . path . dirname ( self . _path ) )
avail = vfs [ statvfs . F_FRSIZE ] * vfs [ statvfs . F_BAVAIL ]
need = long ( self . _size * 1024 L * 1024 L * 1024 L )
if need > avail :
if self . _sparse :
msg = _ ( " The filesystem will not have enough free space "
" to fully allocate the sparse file when the guest "
" is running. " )
else :
ret = True
msg = _ ( " There is not enough free space to create the disk. " )
if msg :
msg + = ( _ ( " %d M requested > %d M available " ) %
( ( need / ( 1024 * 1024 ) ) , ( avail / ( 1024 * 1024 ) ) ) )
return ( ret , msg )
#############################
# Storage creation routines #
#############################
def create ( self , progresscb ) :
if self . fake :
raise RuntimeError ( " Storage creator is fake but creation "
" requested. " )
# If a clone_path is specified, but not vol_install.input_vol,
# that means we are cloning unmanaged -> managed, so skip this
if ( self . _vol_install and
( not self . _clone_path or self . _vol_install . input_vol ) ) :
return self . _vol_install . install ( meter = progresscb )
if self . _clone_path :
text = ( _ ( " Cloning %(srcfile)s " ) %
{ ' srcfile ' : os . path . basename ( self . _clone_path ) } )
else :
text = _ ( " Creating storage file %s " ) % os . path . basename ( self . _path )
size_bytes = long ( self . _size * 1024 L * 1024 L * 1024 L )
progresscb . start ( filename = self . _path , size = long ( size_bytes ) ,
text = text )
if self . _clone_path :
# Plain file clone
self . _clone_local ( progresscb , size_bytes )
else :
# Plain file creation
self . _create_local_file ( progresscb , size_bytes )
def _create_local_file ( self , progresscb , size_bytes ) :
"""
Helper function which attempts to build self . path
"""
fd = None
path = self . _path
sparse = self . _sparse
try :
try :
fd = os . open ( path , os . O_WRONLY | os . O_CREAT | os . O_DSYNC )
if sparse :
os . ftruncate ( fd , size_bytes )
else :
# 1 meg of nulls
mb = 1024 * 1024
buf = ' \x00 ' * mb
left = size_bytes
while left > 0 :
if left < mb :
buf = ' \x00 ' * left
left = max ( left - mb , 0 )
os . write ( fd , buf )
progresscb . update ( size_bytes - left )
except OSError , e :
raise RuntimeError ( _ ( " Error creating diskimage %s : %s " ) %
( path , str ( e ) ) )
finally :
if fd is not None :
os . close ( fd )
progresscb . end ( size_bytes )
def _clone_local ( self , meter , size_bytes ) :
if self . _clone_path == " /dev/null " :
# Not really sure why this check is here,
# but keeping for compat
logging . debug ( " Source dev was /dev/null. Skipping " )
return
if self . _clone_path == self . _path :
logging . debug ( " Source and destination are the same. Skipping. " )
return
# if a destination file exists and sparse flg is True,
# this priority takes a existing file.
if ( not os . path . exists ( self . _path ) and self . _sparse ) :
clone_block_size = 4096
sparse = True
fd = None
try :
fd = os . open ( self . _path , os . O_WRONLY | os . O_CREAT )
os . ftruncate ( fd , size_bytes )
finally :
if fd :
os . close ( fd )
else :
clone_block_size = 1024 * 1024 * 10
sparse = False
logging . debug ( " Local Cloning %s to %s , sparse= %s , block_size= %s " ,
self . _clone_path , self . _path , sparse , clone_block_size )
zeros = ' \0 ' * 4096
src_fd , dst_fd = None , None
try :
try :
src_fd = os . open ( self . _clone_path , os . O_RDONLY )
dst_fd = os . open ( self . _path , os . O_WRONLY | os . O_CREAT )
i = 0
while 1 :
l = os . read ( src_fd , clone_block_size )
s = len ( l )
if s == 0 :
meter . end ( size_bytes )
break
# check sequence of zeros
if sparse and zeros == l :
os . lseek ( dst_fd , s , 1 )
else :
b = os . write ( dst_fd , l )
if s != b :
meter . end ( i )
break
i + = s
if i < size_bytes :
meter . update ( i )
except OSError , e :
raise RuntimeError ( _ ( " Error cloning diskimage %s to %s : %s " ) %
( self . _clone_path , self . _path , str ( e ) ) )
finally :
if src_fd is not None :
os . close ( src_fd )
if dst_fd is not None :
os . close ( dst_fd )
class StorageBackend ( _StorageBase ) :
"""
Class that carries all the info about any existing storage that
the disk references
"""
def __init__ ( self , conn , path , vol_object , pool_object ) :
_StorageBase . __init__ ( self )
self . _conn = conn
self . _vol_object = vol_object
self . _pool_object = pool_object
self . _path = path
if self . _vol_object is not None :
self . _pool_object = None
self . _path = None
elif self . _pool_object is not None :
if self . _path is None :
raise ValueError ( " path must be specified is backend is "
" pool object. " )
# Cached bits
self . _pool_xml = None
self . _vol_xml = None
self . _exists = None
self . _size = None
self . _dev_type = None
################
# Internal API #
################
def _get_pool_xml ( self ) :
if self . _pool_xml is None :
2013-09-23 01:34:53 +04:00
self . _pool_xml = StoragePool ( self . _conn ,
parsexml = self . _pool_object . XMLDesc ( 0 ) )
2013-07-13 18:09:00 +04:00
return self . _pool_xml
def _get_vol_xml ( self ) :
if self . _vol_xml is None :
2013-09-23 01:34:53 +04:00
self . _vol_xml = StorageVolume ( self . _conn ,
parsexml = self . _vol_object . XMLDesc ( 0 ) )
2013-07-13 18:09:00 +04:00
return self . _vol_xml
##############
# Public API #
##############
def _get_path ( self ) :
if self . _vol_object :
return self . _vol_object . path ( )
return self . _path
path = property ( _get_path )
def get_vol_object ( self ) :
return self . _vol_object
def get_size ( self ) :
"""
Return size of existing storage
"""
if self . _size is None :
ret = 0
if self . _vol_object :
2013-09-23 01:34:53 +04:00
ret = self . _get_vol_xml ( ) . capacity
2013-07-13 18:09:00 +04:00
elif self . _pool_object :
2013-09-23 01:34:53 +04:00
ret = self . _get_pool_xml ( ) . capacity
2013-07-13 18:09:00 +04:00
elif self . _path :
ignore , ret = util . stat_disk ( self . path )
self . _size = ( float ( ret ) / 1024.0 / 1024.0 / 1024.0 )
return self . _size
def exists ( self ) :
if self . _exists is None :
if self . path is None :
self . _exists = True
elif self . _vol_object or self . _pool_object :
self . _exists = True
elif not self . _conn . is_remote ( ) and os . path . exists ( self . _path ) :
self . _exists = True
else :
self . _exists = False
return self . _exists
def get_dev_type ( self ) :
"""
Return disk ' type ' value per storage settings
"""
if self . _dev_type is None :
if self . _vol_object :
t = self . _vol_object . info ( ) [ 0 ]
if t == libvirt . VIR_STORAGE_VOL_FILE :
self . _dev_type = " file "
elif t == libvirt . VIR_STORAGE_VOL_BLOCK :
self . _dev_type = " block "
else :
self . _dev_type = " file "
elif self . _pool_object :
2013-09-23 01:34:53 +04:00
self . _dev_type = self . _get_pool_xml ( ) . get_vm_disk_type ( )
2013-07-13 18:09:00 +04:00
elif self . _path :
if os . path . isdir ( self . _path ) :
self . _dev_type = " dir "
elif util . stat_disk ( self . _path ) [ 0 ] :
self . _dev_type = " file "
else :
self . _dev_type = " block "
if not self . _dev_type :
self . _dev_type = " block "
return self . _dev_type
def get_driver_type ( self ) :
if self . _vol_object :
2013-09-23 01:34:53 +04:00
return self . _get_vol_xml ( ) . format
2013-07-13 18:09:00 +04:00
return None
def is_managed ( self ) :
return bool ( self . _vol_object or self . _pool_object )