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
2014-12-09 02:18:59 +03:00
import re
2013-07-13 18:09:00 +04:00
import statvfs
import libvirt
2014-09-12 23:59:22 +04:00
from . import util
from . storage import StoragePool , StorageVolume
2013-07-13 18:09:00 +04:00
2014-12-05 04:13:53 +03:00
def _lookup_pool_by_dirname ( conn , path ) :
2013-07-13 18:09:00 +04:00
"""
2014-12-05 04:13:53 +03:00
Try to find the parent pool for the passed path .
If found , and the pool isn ' t running, attempt to start it up.
return pool , or None if not found
2013-07-13 18:09:00 +04:00
"""
2014-12-05 04:13:53 +03:00
pool = StoragePool . lookup_pool_by_path ( conn , os . path . dirname ( path ) )
if not pool :
2013-07-13 18:09:00 +04:00
return None
2014-12-05 04:13:53 +03:00
# Ensure pool is running
if pool . info ( ) [ 0 ] != libvirt . VIR_STORAGE_POOL_RUNNING :
pool . create ( 0 )
return pool
def _lookup_vol_by_path ( conn , path ) :
"""
Try to find a volume matching the full passed path . Call info ( ) on
it to ensure the volume wasn ' t removed behind libvirt ' s back
"""
try :
vol = conn . storageVolLookupByPath ( path )
vol . info ( )
return vol , None
except libvirt . libvirtError , e :
2015-07-10 15:07:02 +03:00
if ( hasattr ( libvirt , " VIR_ERR_NO_STORAGE_VOL " ) and
e . get_error_code ( ) != libvirt . VIR_ERR_NO_STORAGE_VOL ) :
2014-12-05 04:13:53 +03:00
raise
return None , e
def _lookup_vol_by_basename ( pool , path ) :
"""
Try to lookup a volume for ' path ' in parent ' pool ' by it ' s filename.
This sometimes works in cases where full volume path lookup doesn ' t,
since not all libvirt storage backends implement path lookup .
"""
name = os . path . basename ( path )
if name in pool . listVolumes ( ) :
2015-04-13 22:59:35 +03:00
return pool . storageVolLookupByName ( name )
2014-12-05 04:13:53 +03:00
def check_if_path_managed ( conn , path ) :
"""
Try to lookup storage objects for the passed path .
Returns ( volume , parent pool ) . Only one is returned at a time .
"""
vol , ignore = _lookup_vol_by_path ( conn , path )
if vol :
2014-12-10 01:03:48 +03:00
return vol , vol . storagePoolLookupByVolume ( )
2014-12-05 04:13:53 +03:00
pool = _lookup_pool_by_dirname ( conn , path )
if not pool :
return None , None
# We have the parent pool, but didn't find a volume on first lookup
# attempt. Refresh the pool and try again, incase we were just out
# of date.
try :
pool . refresh ( 0 )
vol , verr = _lookup_vol_by_path ( conn , path )
if verr :
try :
vol = _lookup_vol_by_basename ( pool , path )
except :
pass
except Exception , e :
vol = None
pool = None
verr = str ( e )
2013-07-13 18:09:00 +04:00
2014-02-09 01:36:45 +04:00
if not vol and not pool and verr :
raise ValueError ( _ ( " Cannot use storage %(path)s : %(err)s " ) %
{ ' path ' : path , ' err ' : verr } )
2014-12-05 03:52:32 +03:00
return vol , pool
2013-07-13 18:09:00 +04:00
2014-02-09 01:36:45 +04:00
def _can_auto_manage ( path ) :
path = path or " "
skip_prefixes = [ " /dev " , " /sys " , " /proc " ]
2014-12-10 17:04:09 +03:00
if path_is_url ( path ) :
return False
2014-02-09 01:36:45 +04:00
for prefix in skip_prefixes :
if path . startswith ( prefix + " / " ) or path == prefix :
return False
return True
def manage_path ( conn , path ) :
"""
If path is not managed , try to create a storage pool to probe the path
"""
2014-12-06 03:52:25 +03:00
if not conn . check_support ( conn . SUPPORT_CONN_STORAGE ) :
return None , None
2014-12-10 17:04:09 +03:00
if not path :
2014-12-09 17:20:18 +03:00
return None , None
2014-12-06 03:52:25 +03:00
2014-12-10 17:04:09 +03:00
if not path_is_url ( path ) :
path = os . path . abspath ( path )
2014-12-05 03:52:32 +03:00
vol , pool = check_if_path_managed ( conn , path )
2014-02-09 01:36:45 +04:00
if vol or pool or not _can_auto_manage ( path ) :
2014-12-05 03:36:26 +03:00
return vol , pool
2014-02-09 01:36:45 +04:00
dirname = os . path . dirname ( path )
2014-04-29 22:09:14 +04:00
poolname = os . path . basename ( dirname ) . replace ( " " , " _ " )
if not poolname :
poolname = " dirpool "
poolname = StoragePool . find_free_name ( conn , poolname )
2014-02-09 01:36:45 +04:00
logging . debug ( " Attempting to build pool= %s target= %s " , poolname , dirname )
poolxml = StoragePool ( conn )
2014-04-29 22:09:14 +04:00
poolxml . name = poolname
2014-02-09 01:36:45 +04:00
poolxml . type = poolxml . TYPE_DIR
poolxml . target_path = dirname
pool = poolxml . install ( build = False , create = True , autostart = True )
conn . clear_cache ( pools = True )
2014-12-05 04:13:53 +03:00
vol = _lookup_vol_by_basename ( pool , path )
2014-12-05 03:36:26 +03:00
return vol , pool
2013-07-13 18:09:00 +04:00
2014-12-09 02:18:59 +03:00
def path_is_url ( path ) :
"""
Detect if path is a URL
"""
if not path :
return False
return bool ( re . match ( " [a-zA-Z]+( \ +[a-zA-Z]+)?://.* " , path ) )
2014-12-08 23:02:27 +03:00
##############################################
# Classes for tracking storage media details #
##############################################
2013-07-13 18:09:00 +04:00
class _StorageBase ( object ) :
2014-12-08 23:02:27 +03:00
"""
Storage base class , defining the API used by VirtualDisk
"""
2014-12-06 05:09:26 +03:00
def __init__ ( self , conn ) :
self . _conn = conn
2014-12-10 02:23:22 +03:00
self . _parent_pool_xml = None
2014-12-06 05:09:26 +03:00
2013-07-13 18:09:00 +04:00
def get_size ( self ) :
raise NotImplementedError ( )
def get_dev_type ( self ) :
raise NotImplementedError ( )
def get_driver_type ( self ) :
raise NotImplementedError ( )
2014-12-09 00:02:26 +03:00
def get_vol_install ( self ) :
raise NotImplementedError ( )
def get_vol_object ( self ) :
raise NotImplementedError ( )
2014-12-10 02:23:22 +03:00
def get_parent_pool ( self ) :
raise NotImplementedError ( )
def get_parent_pool_xml ( self ) :
if not self . _parent_pool_xml and self . get_parent_pool ( ) :
self . _parent_pool_xml = StoragePool ( self . _conn ,
parsexml = self . get_parent_pool ( ) . XMLDesc ( 0 ) )
return self . _parent_pool_xml
2014-12-09 00:02:26 +03:00
def validate ( self , disk ) :
raise NotImplementedError ( )
2014-12-10 00:57:32 +03:00
def get_path ( self ) :
2014-12-09 18:15:27 +03:00
raise NotImplementedError ( )
2014-12-09 00:02:26 +03:00
# Storage creation routines
def is_size_conflict ( self ) :
raise NotImplementedError ( )
def create ( self , progresscb ) :
raise NotImplementedError ( )
def will_create_storage ( self ) :
raise NotImplementedError ( )
2013-07-13 18:09:00 +04:00
2014-12-06 05:09:26 +03:00
class _StorageCreator ( _StorageBase ) :
2014-12-08 23:02:27 +03:00
"""
Base object for classes that will actually create storage on disk
"""
2014-12-06 05:09:26 +03:00
def __init__ ( self , conn ) :
_StorageBase . __init__ ( self , conn )
2013-07-13 18:09:00 +04:00
2014-12-06 05:09:26 +03:00
self . _pool = None
self . _vol_install = None
self . _path = None
self . _size = None
2013-07-13 18:09:00 +04:00
self . _dev_type = None
##############
# Public API #
##############
2014-12-06 05:09:26 +03:00
def create ( self , progresscb ) :
raise NotImplementedError ( )
2014-12-10 00:57:32 +03:00
def get_path ( self ) :
2013-07-13 18:09:00 +04:00
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 ) )
2014-12-10 20:55:08 +03:00
if self . get_dev_type ( ) == " network " :
self . _path = self . _vol_install . name
else :
2015-06-05 17:55:39 +03:00
sep = " / "
if xmlobj . target_path == " " or xmlobj . target_path [ - 1 ] == ' / ' :
sep = " "
self . _path = ( xmlobj . target_path + sep +
2014-12-10 20:55:08 +03:00
self . _vol_install . name )
2013-07-13 18:09:00 +04:00
return self . _path
def get_vol_install ( self ) :
return self . _vol_install
2014-12-10 20:55:08 +03:00
def get_vol_xml ( self ) :
return self . _vol_install
2013-07-13 18:09:00 +04:00
def get_size ( self ) :
2014-02-09 01:36:45 +04:00
if self . _size is None :
2013-07-13 18:09:00 +04:00
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 "
2014-12-10 20:55:08 +03:00
elif ( self . _vol_install . file_type ==
libvirt . VIR_STORAGE_VOL_NETWORK ) :
self . _dev_type = " network "
2013-07-13 18:09:00 +04:00
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 "
2014-12-06 20:41:44 +03:00
def validate ( self , disk ) :
if disk . device in [ " floppy " , " cdrom " ] :
2013-07-13 18:09:00 +04:00
raise ValueError ( _ ( " Cannot create storage for %s device. " ) %
2014-12-06 20:41:44 +03:00
disk . device )
2013-07-13 18:09:00 +04:00
2014-12-09 01:28:29 +03:00
if self . _vol_install :
2014-12-06 20:41:44 +03:00
self . _vol_install . validate ( )
else :
if disk . type == " block " :
raise ValueError ( _ ( " Local block device path ' %s ' must "
2014-12-10 00:57:32 +03:00
" exist. " ) % self . get_path ( ) )
2014-12-06 20:41:44 +03:00
if self . _size is None :
raise ValueError ( _ ( " size is required for non-existent disk "
2014-12-10 00:57:32 +03:00
" ' %s ' " % self . get_path ( ) ) )
2014-12-06 20:41:44 +03:00
err , msg = self . is_size_conflict ( )
if err :
raise ValueError ( msg )
if msg :
logging . warn ( msg )
2013-07-13 18:09:00 +04:00
2014-12-09 00:02:26 +03:00
def will_create_storage ( self ) :
return True
def get_vol_object ( self ) :
return None
def get_parent_pool ( self ) :
if self . _vol_install :
return self . _vol_install . pool
return None
def exists ( self ) :
return False
2014-12-06 05:09:26 +03:00
class CloneStorageCreator ( _StorageCreator ) :
2014-12-08 23:02:27 +03:00
"""
Handles manually copying local files for Cloner
Many clone scenarios will use libvirt storage APIs , which will use
the ManagedStorageCreator
"""
2014-12-06 05:09:26 +03:00
def __init__ ( self , conn , output_path , input_path , size , sparse ) :
_StorageCreator . __init__ ( self , conn )
2013-07-13 18:09:00 +04:00
2014-12-06 05:09:26 +03:00
self . _path = output_path
self . _output_path = output_path
self . _input_path = input_path
self . _size = size
self . _sparse = sparse
def is_size_conflict ( self ) :
2013-07-13 18:09:00 +04:00
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 )
def create ( self , progresscb ) :
2014-02-09 01:36:45 +04:00
text = ( _ ( " Cloning %(srcfile)s " ) %
2014-12-06 05:09:26 +03:00
{ ' srcfile ' : os . path . basename ( self . _input_path ) } )
2013-07-13 18:09:00 +04:00
2014-02-09 01:36:45 +04:00
size_bytes = long ( self . get_size ( ) * 1024 L * 1024 L * 1024 L )
2014-12-06 05:09:26 +03:00
progresscb . start ( filename = self . _output_path , size = long ( size_bytes ) ,
2013-07-13 18:09:00 +04:00
text = text )
2014-02-09 01:36:45 +04:00
# Plain file clone
self . _clone_local ( progresscb , size_bytes )
2013-07-13 18:09:00 +04:00
def _clone_local ( self , meter , size_bytes ) :
2014-12-06 05:09:26 +03:00
if self . _input_path == " /dev/null " :
2013-07-13 18:09:00 +04:00
# Not really sure why this check is here,
# but keeping for compat
logging . debug ( " Source dev was /dev/null. Skipping " )
return
2014-12-06 05:09:26 +03:00
if self . _input_path == self . _output_path :
2013-07-13 18:09:00 +04:00
logging . debug ( " Source and destination are the same. Skipping. " )
return
2014-12-09 10:53:21 +03:00
# If a destination file exists and sparse flag is True,
# this priority takes an existing file.
2013-07-13 18:09:00 +04:00
2014-12-06 05:09:26 +03:00
if ( not os . path . exists ( self . _output_path ) and self . _sparse ) :
2013-07-13 18:09:00 +04:00
clone_block_size = 4096
sparse = True
fd = None
try :
2014-12-06 05:09:26 +03:00
fd = os . open ( self . _output_path , os . O_WRONLY | os . O_CREAT , 0640 )
2013-07-13 18:09:00 +04:00
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 " ,
2014-12-06 05:09:26 +03:00
self . _input_path , self . _output_path ,
sparse , clone_block_size )
2013-07-13 18:09:00 +04:00
zeros = ' \0 ' * 4096
src_fd , dst_fd = None , None
try :
try :
2014-12-06 05:09:26 +03:00
src_fd = os . open ( self . _input_path , os . O_RDONLY )
dst_fd = os . open ( self . _output_path ,
os . O_WRONLY | os . O_CREAT , 0640 )
2013-07-13 18:09:00 +04:00
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 " ) %
2014-12-06 05:09:26 +03:00
( self . _input_path , self . _output_path , str ( e ) ) )
2013-07-13 18:09:00 +04:00
finally :
if src_fd is not None :
os . close ( src_fd )
if dst_fd is not None :
os . close ( dst_fd )
2014-12-06 05:09:26 +03:00
class ManagedStorageCreator ( _StorageCreator ) :
2014-12-08 23:02:27 +03:00
"""
Handles storage creation via libvirt APIs . All the actual creation
logic lives in StorageVolume , this is mostly about pulling out bits
from that class and mapping them to VirtualDisk elements
"""
2014-12-06 05:09:26 +03:00
def __init__ ( self , conn , vol_install ) :
_StorageCreator . __init__ ( self , conn )
self . _pool = vol_install . pool
self . _vol_install = vol_install
def create ( self , progresscb ) :
return self . _vol_install . install ( meter = progresscb )
def is_size_conflict ( self ) :
return self . _vol_install . is_size_conflict ( )
2013-07-13 18:09:00 +04:00
class StorageBackend ( _StorageBase ) :
"""
Class that carries all the info about any existing storage that
the disk references
"""
2014-12-09 02:18:59 +03:00
def __init__ ( self , conn , path , vol_object , parent_pool ) :
2014-12-06 05:09:26 +03:00
_StorageBase . __init__ ( self , conn )
2013-07-13 18:09:00 +04:00
self . _vol_object = vol_object
2014-12-06 03:52:25 +03:00
self . _parent_pool = parent_pool
2013-07-13 18:09:00 +04:00
self . _path = path
if self . _vol_object is not None :
self . _path = None
2014-12-10 01:03:48 +03:00
if self . _vol_object and not self . _parent_pool :
raise RuntimeError (
" programming error: parent_pool must be specified " )
2014-12-10 00:57:32 +03:00
2013-07-13 18:09:00 +04:00
# Cached bits
self . _vol_xml = None
2014-12-10 02:23:22 +03:00
self . _parent_pool_xml = None
2013-07-13 18:09:00 +04:00
self . _exists = None
self . _size = None
self . _dev_type = None
##############
# Public API #
##############
2014-12-10 00:57:32 +03:00
def get_path ( self ) :
2013-07-13 18:09:00 +04:00
if self . _vol_object :
2014-12-10 02:23:22 +03:00
return self . get_vol_xml ( ) . target_path
2013-07-13 18:09:00 +04:00
return self . _path
def get_vol_object ( self ) :
return self . _vol_object
2014-12-10 02:23:22 +03:00
def get_vol_xml ( self ) :
if self . _vol_xml is None :
self . _vol_xml = StorageVolume ( self . _conn ,
parsexml = self . _vol_object . XMLDesc ( 0 ) )
return self . _vol_xml
2014-12-06 03:52:25 +03:00
def get_parent_pool ( self ) :
return self . _parent_pool
2013-07-13 18:09:00 +04:00
def get_size ( self ) :
"""
Return size of existing storage
"""
if self . _size is None :
ret = 0
if self . _vol_object :
2014-12-10 02:23:22 +03:00
ret = self . get_vol_xml ( ) . capacity
2013-07-13 18:09:00 +04:00
elif self . _path :
2014-12-10 00:57:32 +03:00
ignore , ret = util . stat_disk ( self . _path )
2013-07-13 18:09:00 +04:00
self . _size = ( float ( ret ) / 1024.0 / 1024.0 / 1024.0 )
return self . _size
2014-12-06 20:41:44 +03:00
def exists ( self ) :
2013-07-13 18:09:00 +04:00
if self . _exists is None :
2014-12-10 00:57:32 +03:00
if self . _path is None :
2013-07-13 18:09:00 +04:00
self . _exists = True
2014-12-05 03:36:26 +03:00
elif self . _vol_object :
2013-07-13 18:09:00 +04:00
self . _exists = True
2014-12-10 00:57:32 +03:00
elif ( not self . get_dev_type ( ) == " network " and
2014-12-07 02:33:11 +03:00
not self . _conn . is_remote ( ) and
os . path . exists ( self . _path ) ) :
2013-07-13 18:09:00 +04:00
self . _exists = True
2014-12-06 05:09:26 +03:00
elif self . _parent_pool :
self . _exists = False
2014-12-10 00:57:32 +03:00
elif self . get_dev_type ( ) == " network " :
2014-12-07 02:33:11 +03:00
self . _exists = True
2014-12-06 20:41:44 +03:00
elif ( self . _conn . is_remote ( ) and
2014-02-09 01:36:45 +04:00
not _can_auto_manage ( self . _path ) ) :
# This allows users to pass /dev/sdX and we don't try to
# validate it exists on the remote connection, since
# autopooling /dev is perilous. Libvirt will error if
# the device doesn't exist.
self . _exists = True
2013-07-13 18:09:00 +04:00
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 :
2014-12-10 02:23:22 +03:00
if self . get_vol_xml ( ) . type :
self . _dev_type = self . get_vol_xml ( ) . type
2013-07-13 18:09:00 +04:00
else :
2014-12-10 01:50:16 +03:00
t = self . _vol_object . info ( ) [ 0 ]
if t == StorageVolume . TYPE_FILE :
self . _dev_type = " file "
elif t == StorageVolume . TYPE_BLOCK :
self . _dev_type = " block "
elif t == StorageVolume . TYPE_NETWORK :
self . _dev_type = " network "
else :
self . _dev_type = " file "
2013-07-13 18:09:00 +04:00
2014-12-10 00:57:32 +03:00
elif self . _path and path_is_url ( self . _path ) :
self . _dev_type = " network "
elif self . _path and not self . _conn . is_remote ( ) :
2013-07-13 18:09:00 +04:00
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 :
2014-12-10 20:24:16 +03:00
ret = self . get_vol_xml ( ) . format
if ret != " unknown " :
return ret
2013-07-13 18:09:00 +04:00
return None
2014-12-09 00:02:26 +03:00
def validate ( self , disk ) :
ignore = disk
return
def get_vol_install ( self ) :
return None
def is_size_conflict ( self ) :
return ( False , None )
def will_create_storage ( self ) :
return False
def create ( self , progresscb ) :
ignore = progresscb
raise RuntimeError ( " programming error: %s can ' t create storage " %
self . __class__ . __name__ )