2013-03-18 01:06:52 +04:00
#
# Classes for building disk device xml
#
2014-02-12 18:44:40 +04:00
# Copyright 2006-2008, 2012-2014 Red Hat, Inc.
2013-03-18 01:06:52 +04:00
#
2018-04-04 16:35:41 +03:00
# This work is licensed under the GNU GPLv2 or later.
2018-03-20 22:00:02 +03:00
# See the COPYING file in the top-level directory.
2013-03-18 01:06:52 +04:00
2020-01-18 23:22:50 +03:00
import os
2019-06-17 04:12:39 +03:00
from . . logger import log
2013-03-18 01:06:52 +04:00
2018-03-20 19:27:37 +03:00
from . . import diskbackend
2019-06-08 00:32:51 +03:00
from . . import progress
2020-01-29 03:03:44 +03:00
from . . import xmlutil
2020-09-13 16:52:56 +03:00
from . device import Device , DeviceAddress , DeviceSeclabel
2018-03-20 19:27:37 +03:00
from . . xmlbuilder import XMLBuilder , XMLChildProperty , XMLProperty
2013-03-18 01:06:52 +04:00
2013-04-13 22:34:52 +04:00
2018-10-12 00:46:32 +03:00
def _qemu_sanitize_drvtype ( phystype , fmt ) :
2013-03-18 01:06:52 +04:00
"""
Sanitize libvirt storage volume format to a valid qemu driver type
"""
2013-04-13 22:34:52 +04:00
raw_list = [ " iso " ]
2013-03-18 01:06:52 +04:00
2018-03-20 19:18:35 +03:00
if phystype == DeviceDisk . TYPE_BLOCK :
2018-10-12 00:46:32 +03:00
return DeviceDisk . DRIVER_TYPE_RAW
2013-03-18 01:06:52 +04:00
if fmt in raw_list :
2018-03-20 19:18:35 +03:00
return DeviceDisk . DRIVER_TYPE_RAW
2013-03-18 01:06:52 +04:00
return fmt
2013-04-13 22:34:52 +04:00
2018-01-26 13:27:20 +03:00
class _Host ( XMLBuilder ) :
2019-05-15 16:47:53 +03:00
_XML_PROP_ORDER = [ " name " , " port " , " transport " , " socket " ]
2018-03-21 17:53:34 +03:00
XML_NAME = " host "
2018-01-26 13:27:20 +03:00
name = XMLProperty ( " ./@name " )
port = XMLProperty ( " ./@port " , is_int = True )
2019-05-15 16:47:53 +03:00
transport = XMLProperty ( " ./@transport " )
socket = XMLProperty ( " ./@socket " )
2018-01-26 13:27:20 +03:00
2020-09-13 16:52:56 +03:00
class _DiskSourceAddress ( DeviceAddress ) :
pass
2020-11-11 17:41:47 +03:00
class _DiskSource ( XMLBuilder ) :
"""
Class representing disk < source > block , and various helpers
that only operate on < source > contents
"""
_XML_PROP_ORDER = [
" file " , " dev " , " dir " ,
" volume " , " pool " , " protocol " , " name " , " hosts " ,
" type " , " managed " , " namespace " , " address " ]
XML_NAME = " source "
file = XMLProperty ( " ./@file " )
dev = XMLProperty ( " ./@dev " )
dir = XMLProperty ( " ./@dir " )
pool = XMLProperty ( " ./@pool " )
volume = XMLProperty ( " ./@volume " )
hosts = XMLChildProperty ( _Host )
name = XMLProperty ( " ./@name " )
protocol = XMLProperty ( " ./@protocol " )
2022-03-22 12:41:09 +03:00
query = XMLProperty ( " ./@query " )
2020-11-11 17:41:47 +03:00
type = XMLProperty ( " ./@type " )
managed = XMLProperty ( " ./@managed " , is_yesno = True )
namespace = XMLProperty ( " ./@namespace " , is_int = True )
address = XMLChildProperty ( _DiskSourceAddress , is_single = True )
def set_from_url ( self , uri ) :
"""
For a passed in path URI like gluster : / / or https : / / , split it
up and set the < disk > properties directly
"""
from . . uri import URI
uriobj = URI ( uri )
if uriobj . scheme :
self . protocol = uriobj . scheme
if ( ( uriobj . hostname or uriobj . port or uriobj . transport ) and
not self . hosts ) :
self . hosts . add_new ( )
if uriobj . transport :
self . hosts [ 0 ] . transport = uriobj . transport
if uriobj . hostname :
self . hosts [ 0 ] . name = uriobj . hostname
if uriobj . port :
self . hosts [ 0 ] . port = uriobj . port
if uriobj . path :
if self . hosts and self . hosts [ 0 ] . transport :
self . hosts [ 0 ] . socket = uriobj . path
else :
self . name = uriobj . path
if self . name . startswith ( " / " ) :
self . name = self . name [ 1 : ]
2022-03-22 12:41:09 +03:00
if uriobj . query :
self . query = uriobj . query
2020-11-11 17:41:47 +03:00
def build_url_from_network ( self ) :
"""
Build a URL from network contents of < source >
"""
host = _Host ( self . conn )
if self . hosts :
host = self . hosts [ 0 ]
ret = self . protocol or " unknown "
if host . transport :
ret + = " + %s " % host . transport
ret + = " :// "
if host . name :
ret + = host . name
if host . port :
ret + = " : " + str ( host . port )
if self . name :
if not self . name . startswith ( " / " ) :
ret + = " / "
ret + = self . name
elif host . socket :
if not host . socket . startswith ( " / " ) :
ret + = " / "
ret + = host . socket
return ret
def clear_source ( self ) :
"""
Unset all XML properties that describe the actual source media
"""
self . file = None
self . dev = None
self . dir = None
self . volume = None
self . pool = None
self . name = None
self . protocol = None
for h in self . hosts [ : ] :
self . remove_child ( h )
def set_network_from_storage ( self , volxml , poolxml ) :
"""
For the passed pool + vol object combo representing a network
volume , set the < source > elements directly
"""
is_iscsi_direct = poolxml . type == " iscsi-direct "
protocol = poolxml . type
if is_iscsi_direct :
protocol = " iscsi "
self . protocol = protocol
for idx , poolhost in enumerate ( poolxml . hosts ) :
if len ( self . hosts ) < ( idx + 1 ) :
self . hosts . add_new ( )
self . hosts [ idx ] . name = poolhost . name
self . hosts [ idx ] . port = poolhost . port
path = " "
if is_iscsi_direct :
# Vol path is like this:
# ip-10.66.144.87:3260-iscsi-iqn.2017-12.com.virttest:emulated-iscsi-noauth.target2-lun-1
# Always seems to have -iscsi- embedded in it
if " -iscsi-iqn. " in volxml . target_path :
path = volxml . target_path . split ( " -iscsi- " , 1 ) [ - 1 ]
else :
if poolxml . source_name :
path + = poolxml . source_name
if poolxml . source_path :
path + = poolxml . source_path
if not path . endswith ( ' / ' ) :
path + = " / "
path + = volxml . name
self . name = path or None
2018-03-20 19:18:35 +03:00
class DeviceDisk ( Device ) :
2018-03-21 17:53:34 +03:00
XML_NAME = " disk "
2013-03-18 01:06:52 +04:00
2015-09-24 00:26:48 +03:00
DRIVER_NAME_PHY = " phy "
DRIVER_NAME_QEMU = " qemu "
DRIVER_TYPE_RAW = " raw "
2013-03-18 01:06:52 +04:00
CACHE_MODE_NONE = " none "
CACHE_MODE_WRITETHROUGH = " writethrough "
CACHE_MODE_WRITEBACK = " writeback "
2013-09-30 21:56:42 +04:00
CACHE_MODE_DIRECTSYNC = " directsync "
CACHE_MODE_UNSAFE = " unsafe "
2018-09-06 23:45:44 +03:00
CACHE_MODES = [ CACHE_MODE_NONE , CACHE_MODE_WRITETHROUGH ,
2013-09-30 21:56:42 +04:00
CACHE_MODE_WRITEBACK , CACHE_MODE_DIRECTSYNC , CACHE_MODE_UNSAFE ]
2013-03-18 01:06:52 +04:00
2014-04-29 18:29:33 +04:00
DISCARD_MODE_IGNORE = " ignore "
DISCARD_MODE_UNMAP = " unmap "
2018-09-06 23:45:44 +03:00
DISCARD_MODES = [ DISCARD_MODE_IGNORE , DISCARD_MODE_UNMAP ]
2014-04-29 18:29:33 +04:00
2013-03-18 01:06:52 +04:00
DEVICE_DISK = " disk "
DEVICE_LUN = " lun "
DEVICE_CDROM = " cdrom "
DEVICE_FLOPPY = " floppy "
TYPE_FILE = " file "
TYPE_BLOCK = " block "
TYPE_DIR = " dir "
2014-09-26 17:24:34 +04:00
TYPE_VOLUME = " volume "
2014-12-07 02:33:11 +03:00
TYPE_NETWORK = " network "
2013-03-18 01:06:52 +04:00
IO_MODE_NATIVE = " native "
2020-01-29 04:11:47 +03:00
2013-08-17 18:35:44 +04:00
2013-03-18 01:06:52 +04:00
@staticmethod
2014-12-05 04:00:34 +03:00
def path_definitely_exists ( conn , path ) :
2013-03-18 01:06:52 +04:00
"""
2014-12-05 04:00:34 +03:00
Check if path exists .
return True if we are certain , False otherwise . Path may in fact
exist if we return False , but we can ' t exhaustively know in all
cases .
( In fact if cached storage volume data is out of date , the volume
2016-06-07 18:28:26 +03:00
may have disappeared behind our back , but that shouldn ' t have bad
2014-12-05 04:00:34 +03:00
effects in practice . )
2013-03-18 01:06:52 +04:00
"""
2018-10-12 02:20:46 +03:00
return diskbackend . path_definitely_exists ( conn , path )
2013-03-18 01:06:52 +04:00
@staticmethod
2018-10-12 01:52:45 +03:00
def check_path_search ( conn , path ) :
2013-03-18 01:06:52 +04:00
"""
2018-10-12 01:52:45 +03:00
Check if the connection DAC user has search permissions for all the
directories in the passed path .
2013-03-18 01:06:52 +04:00
2018-10-12 01:52:45 +03:00
: returns : Class with :
- List of the directories the user cannot search , or empty list
- username we checked for or None if not applicable
- uid we checked for or None if not application
2013-03-18 01:06:52 +04:00
"""
2020-11-10 18:11:15 +03:00
log . debug ( " DeviceDisk.check_path_search path= %s " , path )
2018-10-12 01:52:45 +03:00
class SearchData ( object ) :
def __init__ ( self ) :
self . user = None
self . uid = None
self . fixlist = [ ]
searchdata = SearchData ( )
2020-11-10 18:11:15 +03:00
if not path :
2018-10-12 01:52:45 +03:00
return searchdata
2020-01-18 23:22:50 +03:00
2013-07-06 04:36:28 +04:00
if conn . is_remote ( ) :
2018-10-12 01:52:45 +03:00
return searchdata
2020-02-03 03:43:06 +03:00
if not conn . is_qemu_privileged ( ) :
2018-10-12 01:52:45 +03:00
return searchdata
2014-12-09 17:20:18 +03:00
if diskbackend . path_is_url ( path ) :
2018-10-12 01:52:45 +03:00
return searchdata
2018-10-09 23:30:09 +03:00
if diskbackend . path_is_network_vol ( conn , path ) :
return searchdata
2020-01-29 03:03:44 +03:00
path = os . path . abspath ( path )
2013-03-18 01:06:52 +04:00
2018-10-12 01:52:45 +03:00
user , uid = conn . caps . host . get_qemu_baselabel ( )
if not user :
2020-01-31 00:17:42 +03:00
return searchdata
2020-07-18 01:27:14 +03:00
if uid == 0 and not xmlutil . in_testsuite ( ) :
return searchdata # pragma: no cover
2013-03-18 01:06:52 +04:00
2018-10-12 01:52:45 +03:00
searchdata . user = user
searchdata . uid = uid
searchdata . fixlist = diskbackend . is_path_searchable ( path , uid , user )
searchdata . fixlist . reverse ( )
return searchdata
2014-02-11 03:08:59 +04:00
2013-03-18 01:06:52 +04:00
@staticmethod
2020-01-29 03:03:44 +03:00
def fix_path_search ( searchdata ) :
2013-03-18 01:06:52 +04:00
"""
2018-10-12 01:52:45 +03:00
Try to fix any permission problems found by check_path_search
2013-03-18 01:06:52 +04:00
2018-02-14 15:17:31 +03:00
: returns : Return a dictionary of entries { broken path : error msg }
2013-03-18 01:06:52 +04:00
"""
2018-10-12 01:52:45 +03:00
errdict = diskbackend . set_dirs_searchable (
2018-10-12 19:58:51 +03:00
searchdata . fixlist , searchdata . user )
2013-03-18 01:06:52 +04:00
return errdict
2024-07-01 08:28:22 +03:00
@staticmethod
def get_volmap ( conn ) :
return dict ( ( vol . backing_store , vol )
for vol in conn . fetch_all_vols ( ) if vol . backing_store )
2013-03-18 01:06:52 +04:00
@staticmethod
2014-01-15 01:32:05 +04:00
def path_in_use_by ( conn , path , shareable = False , read_only = False ) :
2013-03-18 01:06:52 +04:00
"""
Return a list of VM names that are using the passed path .
2018-02-14 15:17:31 +03:00
: param conn : virConnect to check VMs
: param path : Path to check for
: param shareable : Path we are checking is marked shareable , so
2014-01-15 01:32:05 +04:00
don ' t warn if it conflicts with another shareable source.
2018-02-14 15:17:31 +03:00
: param read_only : Path we are checking is marked read_only , so
2014-01-15 01:32:05 +04:00
don ' t warn if it conflicts with another read_only source.
2013-03-18 01:06:52 +04:00
"""
2024-07-01 08:28:22 +03:00
volmap = DeviceDisk . get_volmap ( conn )
return DeviceDisk . _path_in_use_by ( conn , path , volmap , shareable , read_only )
@staticmethod
def paths_in_use_by ( conn , paths , shareable = False , read_only = False ) :
"""
Return a list of lists of VM names that are using the passed paths .
When handling a list of paths , this method is faster than calling
2024-08-26 18:36:01 +03:00
path_in_use_by ( ) separately as it takes time to call conn . fetch_all_vols ( ) .
2024-07-01 08:28:22 +03:00
: param conn : virConnect to check VMs
: param paths : Paths to check for
: param shareable : Path we are checking is marked shareable , so
don ' t warn if it conflicts with another shareable source.
: param read_only : Path we are checking is marked read_only , so
don ' t warn if it conflicts with another read_only source.
"""
volmap = DeviceDisk . get_volmap ( conn )
ret = [ ]
for path in paths :
ret . append ( DeviceDisk . _path_in_use_by ( conn , path , volmap , shareable , read_only ) )
return ret
@staticmethod
def _path_in_use_by ( conn , path , volmap , shareable = False , read_only = False ) :
2013-03-18 01:06:52 +04:00
if not path :
2014-01-15 01:32:05 +04:00
return [ ]
2013-09-29 04:05:13 +04:00
2014-01-15 02:44:14 +04:00
# Find all volumes that have 'path' somewhere in their backing chain
2013-09-29 04:05:13 +04:00
vols = [ ]
2014-01-15 02:44:14 +04:00
backpath = path
while backpath in volmap :
vol = volmap [ backpath ]
if vol in vols :
2020-01-29 03:03:44 +03:00
break # pragma: no cover
2014-01-15 02:44:14 +04:00
backpath = vol . target_path
vols . append ( backpath )
2013-03-18 01:06:52 +04:00
2014-01-15 01:32:05 +04:00
ret = [ ]
2018-08-31 22:20:50 +03:00
vms = conn . fetch_all_domains ( )
2013-03-18 01:06:52 +04:00
for vm in vms :
2014-01-15 01:32:05 +04:00
if not read_only :
if path in [ vm . os . kernel , vm . os . initrd , vm . os . dtb ] :
ret . append ( vm . name )
continue
2013-09-29 04:05:13 +04:00
2018-03-21 00:23:34 +03:00
for disk in vm . devices . disk :
2020-11-11 23:38:34 +03:00
checkpath = disk . get_source_path ( )
if checkpath in vols and vm . name not in ret :
2014-01-15 01:32:05 +04:00
# VM uses the path indirectly via backing store
2013-09-29 04:05:13 +04:00
ret . append ( vm . name )
2014-01-15 01:32:05 +04:00
break
2020-11-11 23:38:34 +03:00
if checkpath != path :
2014-01-15 01:32:05 +04:00
continue
if shareable and disk . shareable :
continue
if read_only and disk . read_only :
continue
ret . append ( vm . name )
break
2013-09-29 04:05:13 +04:00
return ret
2013-03-18 01:06:52 +04:00
2013-07-26 00:21:30 +04:00
@staticmethod
2014-12-06 05:09:26 +03:00
def build_vol_install ( conn , volname , poolobj , size , sparse ,
2016-06-17 03:08:53 +03:00
fmt = None , backing_store = None , backing_format = None ) :
2014-12-06 05:09:26 +03:00
"""
2018-03-20 19:18:35 +03:00
Helper for building a StorageVolume instance to pass to DeviceDisk
2014-12-06 05:09:26 +03:00
for eventual storage creation .
: param volname : name of the volume to be created
: param size : size in bytes
"""
2018-03-20 19:27:37 +03:00
from . . storage import StorageVolume
2014-12-06 05:09:26 +03:00
if size is None :
raise ValueError ( _ ( " Size must be specified for non "
" existent volume ' %s ' " % volname ) )
2014-12-06 21:04:59 +03:00
# This catches --disk /dev/idontexist,size=1 if /dev is unmanaged
if not poolobj :
raise RuntimeError ( _ ( " Don ' t know how to create storage for "
" path ' %s ' . Use libvirt APIs to manage the parent directory "
" as a pool first. " ) % volname )
2019-06-17 04:12:39 +03:00
log . debug ( " Creating volume ' %s ' on pool ' %s ' " ,
2014-12-06 05:09:26 +03:00
volname , poolobj . name ( ) )
cap = ( size * 1024 * 1024 * 1024 )
if sparse :
alloc = 0
else :
alloc = cap
volinst = StorageVolume ( conn )
volinst . pool = poolobj
volinst . name = volname
volinst . capacity = cap
volinst . allocation = alloc
volinst . backing_store = backing_store
2016-06-17 03:08:53 +03:00
volinst . backing_format = backing_format
2014-12-06 05:09:26 +03:00
if fmt :
2019-07-02 23:56:35 +03:00
if not volinst . supports_format ( ) :
2014-12-06 05:09:26 +03:00
raise ValueError ( _ ( " Format attribute not supported for this "
" volume type " ) )
volinst . format = fmt
return volinst
2013-07-15 17:49:46 +04:00
2013-10-03 15:20:54 +04:00
@staticmethod
def num_to_target ( num ) :
"""
Convert an index in range ( 1 , 1024 ) to a disk / dev number
( like hda , hdb , hdaa , etc . )
"""
digits = [ ]
for factor in range ( 0 , 3 ) :
2017-10-11 14:35:55 +03:00
amt = ( num % ( 26 * * ( factor + 1 ) ) ) / / ( 26 * * factor )
2013-10-03 15:20:54 +04:00
if amt == 0 and num > = ( 26 * * ( factor + 1 ) ) :
amt = 26
num - = amt
digits . insert ( 0 , amt )
gen_t = " "
for digit in digits :
if digit == 0 :
2020-01-29 03:46:16 +03:00
continue
2013-10-03 15:20:54 +04:00
gen_t + = " %c " % ( ord ( ' a ' ) + digit - 1 )
return gen_t
2013-07-15 17:49:46 +04:00
2014-02-12 18:44:40 +04:00
@staticmethod
def target_to_num ( tgt ) :
"""
Convert disk / dev number ( like hda , hdb , hdaa , etc . ) to an index
"""
num = 0
2014-02-23 09:31:48 +04:00
k = 0
2014-02-12 18:44:40 +04:00
if tgt [ 0 ] == ' x ' :
# This case is here for 'xvda'
tgt = tgt [ 1 : ]
for i , c in enumerate ( reversed ( tgt [ 2 : ] ) ) :
2014-02-23 09:31:48 +04:00
if i != 0 :
k = 1
num + = ( ord ( c ) - ord ( ' a ' ) + k ) * ( 26 * * i )
2014-02-12 18:44:40 +04:00
return num
2013-07-16 20:48:52 +04:00
_XML_PROP_ORDER = [
2020-01-29 14:18:15 +03:00
" _xmltype " , " _device " , " snapshot_policy " ,
2013-07-16 20:48:52 +04:00
" driver_name " , " driver_type " ,
2017-07-15 20:38:39 +03:00
" driver_cache " , " driver_discard " , " driver_detect_zeroes " ,
2021-07-26 19:44:45 +03:00
" driver_io " , " driver_iothread " , " driver_queues " , " error_policy " ,
2018-01-26 13:27:20 +03:00
" auth_username " , " auth_secret_type " , " auth_secret_uuid " ,
2020-11-11 17:41:47 +03:00
" source " ,
2014-12-04 21:51:23 +03:00
" target " , " bus " ,
2013-07-16 20:48:52 +04:00
]
2013-07-14 23:06:40 +04:00
2013-09-11 19:47:09 +04:00
def __init__ ( self , * args , * * kwargs ) :
2018-03-20 19:18:35 +03:00
Device . __init__ ( self , * args , * * kwargs )
2013-03-18 01:06:52 +04:00
2017-04-27 22:44:46 +03:00
self . _source_volume_err = None
2015-09-06 20:42:07 +03:00
self . storage_was_created = False
2013-07-13 18:09:00 +04:00
2020-01-29 14:18:15 +03:00
self . _storage_backend = diskbackend . StorageBackendStub (
self . conn , self . _get_xmlpath ( ) , self . _xmltype , self . driver_type )
2013-03-18 01:06:52 +04:00
2020-11-11 23:13:35 +03:00
##################
# XML properties #
##################
_xmltype = XMLProperty ( " ./@type " )
_device = XMLProperty ( " ./@device " )
driver_name = XMLProperty ( " ./driver/@name " )
driver_type = XMLProperty ( " ./driver/@type " )
source = XMLChildProperty ( _DiskSource , is_single = True )
auth_username = XMLProperty ( " ./auth/@username " )
auth_secret_type = XMLProperty ( " ./auth/secret/@type " )
auth_secret_uuid = XMLProperty ( " ./auth/secret/@uuid " )
snapshot_policy = XMLProperty ( " ./@snapshot " )
driver_copy_on_read = XMLProperty ( " ./driver/@copy_on_read " , is_onoff = True )
sgio = XMLProperty ( " ./@sgio " )
rawio = XMLProperty ( " ./@rawio " )
bus = XMLProperty ( " ./target/@bus " )
target = XMLProperty ( " ./target/@dev " )
removable = XMLProperty ( " ./target/@removable " , is_onoff = True )
2021-05-20 09:48:27 +03:00
rotation_rate = XMLProperty ( " ./target/@rotation_rate " , is_int = True )
2020-11-11 23:13:35 +03:00
read_only = XMLProperty ( " ./readonly " , is_bool = True )
shareable = XMLProperty ( " ./shareable " , is_bool = True )
2021-05-26 04:22:10 +03:00
transient = XMLProperty ( " ./transient " , is_bool = True )
2021-05-26 04:22:11 +03:00
transient_shareBacking = XMLProperty ( " ./transient/@shareBacking " , is_yesno = True )
2020-11-11 23:13:35 +03:00
driver_cache = XMLProperty ( " ./driver/@cache " )
driver_discard = XMLProperty ( " ./driver/@discard " )
driver_detect_zeroes = XMLProperty ( " ./driver/@detect_zeroes " )
driver_io = XMLProperty ( " ./driver/@io " )
driver_iothread = XMLProperty ( " ./driver/@iothread " , is_int = True )
2021-07-26 19:44:45 +03:00
driver_queues = XMLProperty ( " ./driver/@queues " , is_int = True )
2023-07-11 03:26:52 +03:00
driver_discard_no_unref = XMLProperty ( " ./driver/@discard_no_unref " , is_onoff = True )
2020-11-11 23:13:35 +03:00
2022-08-16 07:59:57 +03:00
driver_metadata_cache_max_size = XMLProperty (
" ./driver/metadata_cache/max_size " , is_int = True )
driver_metadata_cache_max_size_unit = XMLProperty (
" ./driver/metadata_cache/max_size/@unit " )
2020-11-11 23:13:35 +03:00
error_policy = XMLProperty ( " ./driver/@error_policy " )
serial = XMLProperty ( " ./serial " )
wwn = XMLProperty ( " ./wwn " )
startup_policy = XMLProperty ( " ./source/@startupPolicy " )
logical_block_size = XMLProperty ( " ./blockio/@logical_block_size " )
physical_block_size = XMLProperty ( " ./blockio/@physical_block_size " )
iotune_rbs = XMLProperty ( " ./iotune/read_bytes_sec " , is_int = True )
iotune_ris = XMLProperty ( " ./iotune/read_iops_sec " , is_int = True )
iotune_tbs = XMLProperty ( " ./iotune/total_bytes_sec " , is_int = True )
iotune_tis = XMLProperty ( " ./iotune/total_iops_sec " , is_int = True )
iotune_wbs = XMLProperty ( " ./iotune/write_bytes_sec " , is_int = True )
iotune_wis = XMLProperty ( " ./iotune/write_iops_sec " , is_int = True )
seclabels = XMLChildProperty ( DeviceSeclabel , relative_xpath = " ./source " )
geometry_cyls = XMLProperty ( " ./geometry/@cyls " , is_int = True )
geometry_heads = XMLProperty ( " ./geometry/@heads " , is_int = True )
geometry_secs = XMLProperty ( " ./geometry/@secs " , is_int = True )
geometry_trans = XMLProperty ( " ./geometry/@trans " )
reservations_managed = XMLProperty ( " ./source/reservations/@managed " )
reservations_source_type = XMLProperty ( " ./source/reservations/source/@type " )
reservations_source_path = XMLProperty ( " ./source/reservations/source/@path " )
reservations_source_mode = XMLProperty ( " ./source/reservations/source/@mode " )
2013-07-15 02:14:55 +04:00
#############################
2020-11-11 23:13:35 +03:00
# Internal defaults helpers #
2013-07-15 02:14:55 +04:00
#############################
2013-03-18 01:06:52 +04:00
2020-11-11 23:13:35 +03:00
def _get_default_type ( self ) :
if self . source . pool or self . source . volume :
return DeviceDisk . TYPE_VOLUME
if not self . _storage_backend . is_stub ( ) :
return self . _storage_backend . get_dev_type ( )
if self . source . protocol :
return DeviceDisk . TYPE_NETWORK
return self . TYPE_FILE
def _get_default_driver_name ( self ) :
2020-11-11 23:38:34 +03:00
if self . is_empty ( ) :
2020-11-11 23:13:35 +03:00
return None
# Recommended xen defaults from here:
# https://bugzilla.redhat.com/show_bug.cgi?id=1171550#c9
# If type block, use name=phy. Otherwise do the same as qemu
if self . conn . is_xen ( ) and self . type == self . TYPE_BLOCK :
return self . DRIVER_NAME_PHY
if self . conn . support . conn_disk_driver_name_qemu ( ) :
return self . DRIVER_NAME_QEMU
return None
def _get_default_driver_type ( self ) :
"""
Set driver type from passed parameters
Where possible , we want to force / driver / @type = " raw " if installing
a QEMU VM . Without telling QEMU to expect a raw file , the emulator
is forced to autodetect , which has security implications :
https : / / lists . gnu . org / archive / html / qemu - devel / 2008 - 04 / msg00675 . html
"""
if self . driver_name != self . DRIVER_NAME_QEMU :
return None
drvtype = self . _storage_backend . get_driver_type ( )
return _qemu_sanitize_drvtype ( self . type , drvtype )
def _get_type ( self ) :
if self . _xmltype :
return self . _xmltype
return self . _get_default_type ( )
def _set_type ( self , val ) :
self . _xmltype = val
type = property ( _get_type , _set_type )
def _get_device ( self ) :
if self . _device :
return self . _device
return self . DEVICE_DISK
def _set_device ( self , val ) :
self . _device = val
device = property ( _get_device , _set_device )
############################
# Storage backend handling #
############################
def _change_backend ( self , path , vol_object , parent_pool ) :
backend = diskbackend . StorageBackend ( self . conn , path ,
vol_object , parent_pool )
self . _storage_backend = backend
def set_backend_for_existing_path ( self ) :
# This is an entry point for parsexml Disk instances to request
# a _storage_backend to be initialized from the XML path. That
# will cause validate() to actually validate the path exists.
# We need this so addhw XML editing will still validate the disk path
if self . _storage_backend . is_stub ( ) :
self . _resolve_storage_backend ( )
def _resolve_storage_backend ( self ) :
"""
Convert the relevant < source > XML values into self . _storage_backend
"""
path = None
vol_object = None
parent_pool = None
self . _source_volume_err = None
typ = self . _get_default_type ( )
if self . type == DeviceDisk . TYPE_NETWORK :
# Fill in a completed URL for virt-manager UI, path comparison, etc
path = self . source . build_url_from_network ( )
if typ == DeviceDisk . TYPE_VOLUME :
try :
parent_pool = self . conn . storagePoolLookupByName (
self . source . pool )
vol_object = parent_pool . storageVolLookupByName (
self . source . volume )
except Exception as e :
self . _source_volume_err = str ( e )
log . debug ( " Error fetching source pool= %s vol= %s " ,
self . source . pool , self . source . volume , exc_info = True )
if vol_object is None and path is None :
path = self . _get_xmlpath ( )
if path and not vol_object and not parent_pool :
2022-02-27 20:08:30 +03:00
( dummy , vol_object , parent_pool ) = diskbackend . manage_path (
2020-11-11 23:13:35 +03:00
self . conn , path )
self . _change_backend ( path , vol_object , parent_pool )
2020-11-11 23:38:34 +03:00
def get_source_path ( self ) :
"""
Source path is a single string representation of the disk source
storage . For regular storage this is a FS path . For type = network
this is a reconstructed URL . In some cases like rbd : / / this may
be an entirely synthetic URL format
"""
2020-01-29 14:18:15 +03:00
if ( self . _storage_backend . is_stub ( ) and not
self . _storage_backend . get_path ( ) ) :
self . _resolve_storage_backend ( )
2014-12-10 00:57:32 +03:00
return self . _storage_backend . get_path ( )
2020-11-11 23:38:34 +03:00
def set_source_path ( self , newpath ) :
2024-02-18 15:48:43 +03:00
# Some file managers use 'file://' when passing files to
# virt-manager, we need to strip it from the newpath.
if newpath is not None :
newpath = newpath . removeprefix ( " file:// " )
2020-01-29 14:18:15 +03:00
if self . _storage_backend . will_create_storage ( ) :
2020-07-18 01:58:00 +03:00
raise xmlutil . DevError (
2020-01-29 03:03:44 +03:00
" Can ' t change disk path if storage creation info "
" has been set. " )
2014-12-09 01:28:29 +03:00
2014-12-10 17:04:09 +03:00
# User explicitly changed 'path', so try to lookup its storage
# object since we may need it
2022-02-27 20:08:30 +03:00
( newpath , vol_object , parent_pool ) = diskbackend . manage_path (
self . conn , newpath )
2014-12-09 01:28:29 +03:00
2014-12-09 17:20:18 +03:00
self . _change_backend ( newpath , vol_object , parent_pool )
2020-11-11 23:38:34 +03:00
self . _set_xmlpath ( self . get_source_path ( ) )
2013-07-15 02:14:55 +04:00
2014-12-10 01:03:48 +03:00
def set_vol_object ( self , vol_object , parent_pool ) :
2019-06-17 04:12:39 +03:00
log . debug ( " disk.set_vol_object: volxml= \n %s " ,
2015-01-29 18:20:18 +03:00
vol_object . XMLDesc ( 0 ) )
2019-06-17 04:12:39 +03:00
log . debug ( " disk.set_vol_object: poolxml= \n %s " ,
2015-01-29 18:20:18 +03:00
parent_pool . XMLDesc ( 0 ) )
2014-12-10 01:03:48 +03:00
self . _change_backend ( None , vol_object , parent_pool )
2020-11-11 23:38:34 +03:00
self . _set_xmlpath ( self . get_source_path ( ) )
2013-03-18 01:06:52 +04:00
2014-12-06 04:12:35 +03:00
def set_vol_install ( self , vol_install ) :
2019-06-17 04:12:39 +03:00
log . debug ( " disk.set_vol_install: name= %s poolxml= \n %s " ,
2015-01-29 18:20:18 +03:00
vol_install . name , vol_install . pool . XMLDesc ( 0 ) )
2014-12-09 00:02:26 +03:00
self . _storage_backend = diskbackend . ManagedStorageCreator (
2014-12-06 05:09:26 +03:00
self . conn , vol_install )
2020-11-11 23:38:34 +03:00
self . _set_xmlpath ( self . get_source_path ( ) )
2014-12-06 04:12:35 +03:00
2013-07-15 02:14:55 +04:00
def get_vol_object ( self ) :
return self . _storage_backend . get_vol_object ( )
def get_vol_install ( self ) :
2014-12-09 00:02:26 +03:00
return self . _storage_backend . get_vol_install ( )
2014-12-06 03:52:25 +03:00
def get_parent_pool ( self ) :
return self . _storage_backend . get_parent_pool ( )
2013-07-15 02:14:55 +04:00
def get_size ( self ) :
return self . _storage_backend . get_size ( )
2013-03-18 01:06:52 +04:00
2013-07-15 01:12:47 +04:00
2014-12-10 02:23:22 +03:00
def _set_source_network_from_storage ( self , volxml , poolxml ) :
2020-11-11 17:41:47 +03:00
self . type = " network "
2018-01-26 13:27:20 +03:00
if poolxml . auth_type :
self . auth_username = poolxml . auth_username
self . auth_secret_type = poolxml . auth_type
self . auth_secret_uuid = poolxml . auth_secret_uuid
2014-12-10 02:23:22 +03:00
2020-11-11 17:41:47 +03:00
self . source . set_network_from_storage ( volxml , poolxml )
2014-12-10 02:23:22 +03:00
def _set_network_source_from_backend ( self ) :
if ( self . _storage_backend . get_vol_object ( ) or
self . _storage_backend . get_vol_install ( ) ) :
volxml = self . _storage_backend . get_vol_xml ( )
poolxml = self . _storage_backend . get_parent_pool_xml ( )
self . _set_source_network_from_storage ( volxml , poolxml )
elif self . _storage_backend . get_path ( ) :
2020-11-11 17:41:47 +03:00
self . source . set_from_url ( self . _storage_backend . get_path ( ) )
2014-12-07 02:33:11 +03:00
2014-12-04 21:51:23 +03:00
def _disk_type_to_object_prop_name ( self ) :
disk_type = self . type
2018-03-20 19:18:35 +03:00
if disk_type == DeviceDisk . TYPE_BLOCK :
2020-11-11 17:41:47 +03:00
return " dev "
2018-03-20 19:18:35 +03:00
elif disk_type == DeviceDisk . TYPE_DIR :
2020-11-11 17:41:47 +03:00
return " dir "
2018-03-20 19:18:35 +03:00
elif disk_type == DeviceDisk . TYPE_FILE :
2020-11-11 17:41:47 +03:00
return " file "
2014-12-07 00:22:00 +03:00
return None
2014-12-07 18:08:33 +03:00
# _xmlpath is an abstraction for source file/block/dir paths, since
# they don't have any special properties aside from needing to match
# 'type' value with the source property used.
2014-12-04 21:51:23 +03:00
def _get_xmlpath ( self ) :
2020-11-11 17:41:47 +03:00
if self . source . file :
return self . source . file
if self . source . dev :
return self . source . dev
if self . source . dir :
return self . source . dir
2014-12-07 01:29:05 +03:00
return None
2020-11-11 23:13:35 +03:00
2014-12-04 21:51:23 +03:00
def _set_xmlpath ( self , val ) :
2020-11-11 17:41:47 +03:00
self . source . clear_source ( )
2014-12-04 21:51:23 +03:00
2014-12-10 00:57:32 +03:00
if self . _storage_backend . get_dev_type ( ) == " network " :
2014-12-10 02:23:22 +03:00
self . _set_network_source_from_backend ( )
2014-12-09 17:20:18 +03:00
return
2014-12-04 21:51:23 +03:00
propname = self . _disk_type_to_object_prop_name ( )
2014-12-07 00:22:00 +03:00
if not propname :
return
2020-11-11 17:41:47 +03:00
return setattr ( self . source , propname , val )
2013-07-14 05:04:27 +04:00
2014-12-06 05:09:26 +03:00
def set_local_disk_to_clone ( self , disk , sparse ) :
2013-07-14 05:04:27 +04:00
"""
2014-12-06 05:09:26 +03:00
Set a path to manually clone ( as in , not through libvirt )
2013-07-14 05:04:27 +04:00
"""
2014-12-09 00:02:26 +03:00
self . _storage_backend = diskbackend . CloneStorageCreator ( self . conn ,
2020-11-11 23:38:34 +03:00
self . get_source_path ( ) ,
disk . get_source_path ( ) ,
disk . get_size ( ) , sparse )
2013-07-14 05:04:27 +04:00
2020-11-11 23:13:35 +03:00
#####################
# Utility functions #
#####################
2020-11-11 23:38:34 +03:00
def is_empty ( self ) :
return not bool ( self . get_source_path ( ) )
2013-08-17 22:21:30 +04:00
def is_cdrom ( self ) :
return self . device == self . DEVICE_CDROM
def is_floppy ( self ) :
return self . device == self . DEVICE_FLOPPY
def is_disk ( self ) :
return self . device == self . DEVICE_DISK
2013-07-14 05:04:27 +04:00
2013-03-18 01:06:52 +04:00
def can_be_empty ( self ) :
2020-09-13 16:52:56 +03:00
if self . is_floppy ( ) or self . is_cdrom ( ) :
return True
if self . type in [ " file " , " block " , " dir " , " volume " , " network " ] :
return False
# Don't error for unknown types
return True
2013-03-18 01:06:52 +04:00
2014-12-06 04:25:11 +03:00
def wants_storage_creation ( self ) :
"""
If true , this disk needs storage creation parameters or things
will error .
"""
2020-01-29 14:18:15 +03:00
return not self . _storage_backend . exists ( )
2013-03-18 01:06:52 +04:00
2020-11-11 23:13:35 +03:00
####################
# Storage building #
####################
2013-03-18 01:06:52 +04:00
2018-09-03 23:44:38 +03:00
def build_storage ( self , meter ) :
2013-03-18 01:06:52 +04:00
"""
Build storage ( if required )
If storage doesn ' t exist (a non-existent file ' path ' , or ' vol_install '
was specified ) , we create it .
"""
2020-01-29 14:18:15 +03:00
if not self . _storage_backend . will_create_storage ( ) :
2013-07-13 18:09:00 +04:00
return
2019-06-08 00:32:51 +03:00
meter = progress . ensure_meter ( meter )
2020-07-05 01:02:00 +03:00
# pylint: disable=assignment-from-no-return
2014-12-10 01:03:48 +03:00
vol_object = self . _storage_backend . create ( meter )
2015-09-06 20:42:07 +03:00
self . storage_was_created = True
2014-12-10 01:03:48 +03:00
if not vol_object :
return
parent_pool = self . get_vol_install ( ) . pool
self . _change_backend ( None , vol_object , parent_pool )
2013-03-18 01:06:52 +04:00
2020-11-11 23:13:35 +03:00
######################
# validation helpers #
######################
def validate ( self ) :
2020-11-11 23:38:34 +03:00
if self . is_empty ( ) :
2020-11-11 23:13:35 +03:00
if self . _source_volume_err :
raise RuntimeError ( self . _source_volume_err )
if not self . can_be_empty ( ) :
raise ValueError ( _ ( " Device type ' %s ' requires a path " ) %
self . device )
return
if ( not self . _storage_backend . exists ( ) and
not self . _storage_backend . will_create_storage ( ) ) :
raise ValueError (
_ ( " Must specify storage creation parameters for "
2020-11-11 23:38:34 +03:00
" non-existent path ' %s ' . " ) % self . get_source_path ( ) )
2020-11-11 23:13:35 +03:00
self . _storage_backend . validate ( )
2013-03-18 01:06:52 +04:00
def is_size_conflict ( self ) :
"""
reports if disk size conflicts with available space
returns a two element tuple :
1. first element is True if fatal conflict occurs
2. second element is a string description of the conflict or None
Non fatal conflicts ( sparse disk exceeds available space ) will
return ( False , " description of collision " )
"""
2014-12-09 00:02:26 +03:00
return self . _storage_backend . is_size_conflict ( )
2013-03-18 01:06:52 +04:00
2020-01-29 03:03:44 +03:00
def is_conflict_disk ( self ) :
2013-03-18 01:06:52 +04:00
"""
check if specified storage is in use by any other VMs on passed
connection .
2018-02-14 15:17:31 +03:00
: returns : list of colliding VM names
2013-03-18 01:06:52 +04:00
"""
2020-11-11 23:38:34 +03:00
ret = self . path_in_use_by ( self . conn , self . get_source_path ( ) ,
2014-01-15 01:32:05 +04:00
shareable = self . shareable ,
read_only = self . read_only )
2013-03-18 01:06:52 +04:00
return ret
2020-11-11 23:13:35 +03:00
###########################
# Misc functional helpers #
###########################
def sync_path_props ( self ) :
"""
Fills in the values of type , driver_type , and driver_name for
the associated backing storage . This needs to be manually called
if changing an existing disk ' s media.
"""
path = self . _get_xmlpath ( )
self . type = self . _get_default_type ( )
self . driver_name = self . _get_default_driver_name ( )
self . driver_type = self . _get_default_driver_type ( )
# Need to retrigger this if self.type changed
if path :
self . _set_xmlpath ( path )
2020-01-29 13:49:13 +03:00
def get_target_prefix ( self ) :
2013-03-18 01:06:52 +04:00
"""
Returns the suggested disk target prefix ( hd , xvd , sd . . . ) for the
disk .
2018-02-14 15:17:31 +03:00
: returns : str prefix , or None if no reasonable guess can be made
2013-03-18 01:06:52 +04:00
"""
2018-05-21 22:42:50 +03:00
# The upper limits here aren't necessarily 1024, but let the HV
2013-03-18 01:06:52 +04:00
# error as appropriate.
2014-01-26 06:08:11 +04:00
def _return ( prefix ) :
nummap = {
" vd " : 1024 ,
" xvd " : 1024 ,
" fd " : 2 ,
" hd " : 4 ,
" sd " : 1024 ,
}
return prefix , nummap [ prefix ]
2013-03-18 01:06:52 +04:00
if self . bus == " virtio " :
2014-01-26 06:08:11 +04:00
return _return ( " vd " )
2013-03-18 01:06:52 +04:00
elif self . bus == " xen " :
2014-01-26 06:08:11 +04:00
return _return ( " xvd " )
2013-08-17 22:21:30 +04:00
elif self . bus == " fdc " or self . is_floppy ( ) :
2014-01-26 06:08:11 +04:00
return _return ( " fd " )
2013-03-18 01:06:52 +04:00
elif self . bus == " ide " :
2014-01-26 06:08:11 +04:00
return _return ( " hd " )
2020-01-29 13:49:13 +03:00
# sata, scsi, usb, sd
2014-01-26 06:08:11 +04:00
return _return ( " sd " )
2013-03-18 01:06:52 +04:00
2020-01-29 13:44:49 +03:00
def generate_target ( self , skip_targets ) :
2013-03-18 01:06:52 +04:00
"""
Generate target device ( ' hda ' , ' sdb ' , etc . . ) for disk , excluding
2020-01-29 13:44:49 +03:00
any targets in ' skip_targets ' .
2014-02-12 18:46:35 +04:00
Sets self . target , and returns the generated value .
2013-03-18 01:06:52 +04:00
2018-02-14 15:17:31 +03:00
: param skip_targets : list of targets to exclude
: returns : generated target
2013-03-18 01:06:52 +04:00
"""
2020-01-29 13:49:13 +03:00
prefix , maxnode = self . get_target_prefix ( )
2013-10-03 16:40:24 +04:00
skip_targets = [ t for t in skip_targets if t and t . startswith ( prefix ) ]
skip_targets . sort ( )
2013-03-18 01:06:52 +04:00
2013-10-03 16:40:24 +04:00
def get_target ( ) :
first_found = None
2013-03-18 01:06:52 +04:00
2020-01-29 13:44:49 +03:00
for i in range ( maxnode ) :
2014-02-17 19:41:02 +04:00
gen_t = prefix + self . num_to_target ( i + 1 )
2013-10-03 16:40:24 +04:00
if gen_t in skip_targets :
skip_targets . remove ( gen_t )
continue
if not skip_targets :
return gen_t
elif not first_found :
first_found = gen_t
if first_found :
return first_found
ret = get_target ( )
if ret :
self . target = ret
return ret
2014-02-12 18:46:35 +04:00
2020-07-12 00:31:40 +03:00
raise ValueError (
2020-07-14 10:41:58 +03:00
ngettext ( " Only %(number)s disk for bus ' %(bus)s ' are supported " ,
" Only %(number)s disks for bus ' %(bus)s ' are supported " ,
maxnode ) %
{ " number " : maxnode , " bus " : self . bus } )
2018-09-02 18:57:41 +03:00
2020-01-29 04:03:38 +03:00
def change_bus ( self , guest , newbus ) :
"""
Change the bus value for an existing disk , which has some
follow on side effects .
"""
if self . bus == newbus :
return
oldprefix = self . get_target_prefix ( ) [ 0 ]
self . bus = newbus
self . address . clear ( )
if oldprefix == self . get_target_prefix ( ) [ 0 ] :
return
used = [ disk . target for disk in guest . devices . disk ]
if self . target :
used . remove ( self . target )
self . target = None
self . generate_target ( used )
2018-09-02 18:57:41 +03:00
2020-11-11 23:13:35 +03:00
#########################
# set_defaults handling #
#########################
2018-09-02 18:57:41 +03:00
def _default_bus ( self , guest ) :
if self . is_floppy ( ) :
return " fdc "
if guest . os . is_xenpv ( ) :
return " xen "
if not guest . os . is_hvm ( ) :
# This likely isn't correct, but it's kind of a catch all
# for virt types we don't know how to handle.
return " ide "
if self . is_disk ( ) and guest . supports_virtiodisk ( ) :
return " virtio "
2019-03-08 19:30:24 +03:00
if ( self . is_cdrom ( ) and
guest . supports_virtioscsi ( ) and
not guest . os . is_x86 ( ) ) :
# x86 long time default has been IDE CDROM, stick with that to
# avoid churn, but every newer virt arch that supports virtio-scsi
# should use it
2018-09-02 18:57:41 +03:00
return " scsi "
if guest . os . is_arm ( ) :
return " sd "
if guest . os . is_q35 ( ) :
return " sata "
2021-02-11 18:41:11 +03:00
if self . conn . is_bhyve ( ) :
# IDE bus is not supported by bhyve
return " sata "
2018-09-02 18:57:41 +03:00
return " ide "
def set_defaults ( self , guest ) :
2018-09-02 05:16:26 +03:00
if not self . _device :
self . _device = self . _get_device ( )
2020-01-29 14:18:15 +03:00
if not self . _xmltype :
self . _xmltype = self . _get_default_type ( )
2018-09-02 05:16:26 +03:00
if not self . driver_name :
self . driver_name = self . _get_default_driver_name ( )
if not self . driver_type :
self . driver_type = self . _get_default_driver_type ( )
2018-09-02 18:57:41 +03:00
if not self . bus :
self . bus = self . _default_bus ( guest )
if self . is_cdrom ( ) :
self . read_only = True
2022-02-03 21:30:50 +03:00
discard_unmap = False
if ( self . conn . is_qemu ( ) and
self . is_disk ( ) and
self . _storage_backend . will_create_storage ( ) and
self . _storage_backend . get_vol_install ( ) and
self . _storage_backend . get_vol_install ( ) . allocation == 0 ) :
discard_unmap = True
2018-09-02 18:57:41 +03:00
if ( self . conn . is_qemu ( ) and
self . is_disk ( ) and
self . type == self . TYPE_BLOCK ) :
2022-02-03 21:30:50 +03:00
discard_unmap = True
2018-09-02 18:57:41 +03:00
if not self . driver_cache :
self . driver_cache = self . CACHE_MODE_NONE
if not self . driver_io :
self . driver_io = self . IO_MODE_NATIVE
2022-02-03 21:30:50 +03:00
if discard_unmap :
if not self . driver_discard :
self . driver_discard = " unmap "
2018-09-02 18:57:41 +03:00
if not self . target :
used_targets = [ d . target for d in guest . devices . disk if d . target ]
self . generate_target ( used_targets )