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
import logging
2018-03-20 19:27:37 +03:00
from . . import diskbackend
from . . import util
2018-03-20 19:18:35 +03:00
from . device import Device
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 ) :
_XML_PROP_ORDER = [ " name " , " port " ]
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 )
2016-06-12 00:43:18 +03:00
class _DiskSeclabel ( XMLBuilder ) :
"""
This is for disk source < seclabel > . It ' s similar to a domain
< seclabel > but has fewer options
"""
2018-03-21 17:53:34 +03:00
XML_NAME = " seclabel "
2016-06-12 00:43:18 +03:00
model = XMLProperty ( " ./@model " )
relabel = XMLProperty ( " ./@relabel " , is_yesno = True )
label = XMLProperty ( " ./label " )
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
2018-09-06 23:45:44 +03:00
DETECT_ZEROES_MODE_OFF = " off "
DETECT_ZEROES_MODE_ON = " on "
DETECT_ZEROES_MODE_UNMAP = " unmap "
DETECT_ZEROES_MODES = [ DETECT_ZEROES_MODE_OFF , DETECT_ZEROES_MODE_ON ,
DETECT_ZEROES_MODE_UNMAP ]
2018-08-24 02:40:18 +03: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 "
IO_MODE_THREADS = " threads "
2018-09-06 23:45:44 +03:00
IO_MODES = [ IO_MODE_NATIVE , IO_MODE_THREADS ]
2013-08-17 18:35:44 +04:00
2018-09-06 23:40:49 +03:00
@staticmethod
def get_old_recommended_buses ( guest ) :
ret = [ ]
if guest . os . is_hvm ( ) or guest . conn . is_test ( ) :
if not guest . os . is_q35 ( ) :
ret . append ( " ide " )
ret . append ( " sata " )
ret . append ( " fdc " )
ret . append ( " scsi " )
ret . append ( " usb " )
if guest . type in [ " qemu " , " kvm " , " test " ] :
ret . append ( " sd " )
ret . append ( " virtio " )
if " scsi " not in ret :
ret . append ( " scsi " )
if guest . conn . is_xen ( ) or guest . conn . is_test ( ) :
ret . append ( " xen " )
return ret
@staticmethod
def get_recommended_buses ( guest , domcaps , devtype ) :
# try to get supported disk bus types from domain capabilities
if " bus " in domcaps . devices . disk . enum_names ( ) :
buses = domcaps . devices . disk . get_enum ( " bus " ) . get_values ( )
else :
buses = DeviceDisk . get_old_recommended_buses ( guest )
bus_map = {
" disk " : [ " ide " , " sata " , " scsi " , " sd " , " usb " , " virtio " , " xen " ] ,
" floppy " : [ " fdc " ] ,
" cdrom " : [ " ide " , " sata " , " scsi " ] ,
" lun " : [ " scsi " ] ,
}
return [ bus for bus in buses if bus in bus_map . get ( devtype , [ ] ) ]
2014-09-20 18:23:19 +04:00
@staticmethod
def pretty_disk_bus ( bus ) :
if bus in [ " ide " , " sata " , " scsi " , " usb " , " sd " ] :
return bus . upper ( )
if bus in [ " xen " ] :
return bus . capitalize ( )
if bus == " virtio " :
return " VirtIO "
return bus
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
"""
2018-10-12 01:52:45 +03:00
class SearchData ( object ) :
def __init__ ( self ) :
self . user = None
self . uid = None
self . fixlist = [ ]
searchdata = SearchData ( )
2013-03-18 01:06:52 +04:00
if path is None :
2018-10-12 01:52:45 +03:00
return searchdata
2013-07-06 04:36:28 +04:00
if conn . is_remote ( ) :
2018-10-12 01:52:45 +03:00
return searchdata
if not conn . is_qemu_system ( ) :
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
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 :
return searchdata
if uid == 0 :
return searchdata
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
2018-10-12 01:52:45 +03:00
def fix_path_search ( conn , 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
ignore = conn
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
@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
"""
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
volmap = dict ( ( vol . backing_store , vol )
for vol in conn . fetch_all_vols ( ) if vol . backing_store )
backpath = path
while backpath in volmap :
vol = volmap [ backpath ]
if vol in vols :
break
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 :
2013-09-29 04:05:13 +04:00
if disk . path 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
if disk . path != path :
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 )
2014-12-06 05:09:26 +03:00
logging . debug ( " Creating volume ' %s ' on pool ' %s ' " ,
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 :
if not volinst . supports_property ( " format " ) :
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 )
seen_valid = False
gen_t = " "
for digit in digits :
if digit == 0 :
if not seen_valid :
continue
digit = 1
seen_valid = True
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 = [
2018-09-02 05:16:26 +03:00
" _type " , " _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 " ,
" driver_io " , " error_policy " ,
2014-12-07 00:22:00 +03:00
" _source_file " , " _source_dev " , " _source_dir " ,
2018-01-26 13:27:20 +03:00
" auth_username " , " auth_secret_type " , " auth_secret_uuid " ,
2014-12-07 02:33:11 +03:00
" source_volume " , " source_pool " , " source_protocol " , " source_name " ,
" source_host_name " , " source_host_port " ,
" source_host_transport " , " source_host_socket " ,
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
2014-12-09 01:28:29 +03:00
self . _storage_backend = None
2015-09-06 20:42:07 +03:00
self . storage_was_created = False
2013-07-13 18:09:00 +04:00
2013-03-18 01:06:52 +04:00
2013-07-15 02:14:55 +04:00
#############################
# Public property-esque API #
#############################
2013-03-18 01:06:52 +04:00
def _get_path ( self ) :
2014-12-10 00:57:32 +03:00
if not self . _storage_backend :
xmlpath = self . _get_xmlpath ( )
if xmlpath :
return xmlpath
2014-12-07 02:33:11 +03:00
2014-12-10 00:57:32 +03:00
self . _set_default_storage_backend ( )
return self . _storage_backend . get_path ( )
2014-12-09 17:20:18 +03:00
def _set_path ( self , newpath ) :
2014-12-09 01:28:29 +03:00
if ( self . _storage_backend and
self . _storage_backend . will_create_storage ( ) ) :
2016-02-05 18:18:16 +03:00
raise ValueError ( _ ( " 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
( 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 )
2014-12-04 21:51:23 +03:00
self . _set_xmlpath ( self . path )
2013-07-15 02:14:55 +04:00
path = property ( _get_path , _set_path )
2014-12-10 01:03:48 +03:00
def set_vol_object ( self , vol_object , parent_pool ) :
2015-01-29 18:20:18 +03:00
logging . debug ( " disk.set_vol_object: volxml= \n %s " ,
vol_object . XMLDesc ( 0 ) )
logging . debug ( " disk.set_vol_object: poolxml= \n %s " ,
parent_pool . XMLDesc ( 0 ) )
2014-12-10 01:03:48 +03:00
self . _change_backend ( None , vol_object , parent_pool )
2014-12-06 02:17:39 +03:00
self . _set_xmlpath ( self . path )
2013-03-18 01:06:52 +04:00
2014-12-06 04:12:35 +03:00
def set_vol_install ( self , vol_install ) :
2015-01-29 18:20:18 +03:00
logging . debug ( " disk.set_vol_install: name= %s poolxml= \n %s " ,
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 )
2014-12-06 04:12:35 +03:00
self . _set_xmlpath ( self . path )
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 ) :
2019-01-22 18:28:38 +03:00
if not self . _storage_backend :
return None
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 ) :
if self . get_vol_install ( ) :
return self . get_vol_install ( ) . pool
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
2013-07-15 01:54:01 +04:00
#############################
# Internal defaults helpers #
#############################
2013-07-15 02:00:43 +04:00
def _get_default_driver_name ( self ) :
2013-07-18 01:58:24 +04:00
if not self . path :
return None
2015-09-24 00:26:48 +03:00
# 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 . check_support (
self . conn . SUPPORT_CONN_DISK_DRIVER_NAME_QEMU ) :
return self . DRIVER_NAME_QEMU
2013-07-15 02:00:43 +04:00
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 :
2018-04-30 15:56:53 +03:00
https : / / lists . gnu . org / archive / html / qemu - devel / 2008 - 04 / msg00675 . html
2013-07-15 02:00:43 +04:00
"""
2015-09-24 00:26:48 +03:00
if self . driver_name != self . DRIVER_NAME_QEMU :
2013-07-15 02:00:43 +04:00
return None
2014-12-09 00:02:26 +03:00
drvtype = self . _storage_backend . get_driver_type ( )
2013-07-15 02:00:43 +04:00
return _qemu_sanitize_drvtype ( self . type , drvtype )
2013-07-15 01:12:47 +04:00
2014-12-07 00:22:00 +03:00
#############################
# XML source media handling #
#############################
2013-07-15 02:14:55 +04:00
2014-12-04 21:51:23 +03:00
_source_file = XMLProperty ( " ./source/@file " )
_source_dev = XMLProperty ( " ./source/@dev " )
_source_dir = XMLProperty ( " ./source/@dir " )
2014-12-07 00:22:00 +03:00
source_pool = XMLProperty ( " ./source/@pool " )
source_volume = XMLProperty ( " ./source/@volume " )
2018-01-26 13:27:20 +03:00
auth_username = XMLProperty ( " ./auth/@username " )
auth_secret_type = XMLProperty ( " ./auth/secret/@type " )
auth_secret_uuid = XMLProperty ( " ./auth/secret/@uuid " )
hosts = XMLChildProperty ( _Host , relative_xpath = " ./source " )
2014-12-07 02:33:11 +03:00
source_name = XMLProperty ( " ./source/@name " )
source_protocol = XMLProperty ( " ./source/@protocol " )
# Technically multiple host lines can be listed
source_host_name = XMLProperty ( " ./source/host/@name " )
source_host_port = XMLProperty ( " ./source/host/@port " , is_int = True )
source_host_transport = XMLProperty ( " ./source/host/@transport " )
source_host_socket = XMLProperty ( " ./source/host/@socket " )
2014-12-10 02:23:22 +03:00
def _set_source_network_from_url ( self , uri ) :
2018-03-20 19:27:37 +03:00
from . . uri import URI
2015-09-06 17:36:17 +03:00
uriobj = URI ( uri )
if uriobj . scheme :
self . source_protocol = uriobj . scheme
if uriobj . transport :
self . source_host_transport = uriobj . transport
if uriobj . hostname :
self . source_host_name = uriobj . hostname
if uriobj . port :
self . source_host_port = uriobj . port
if uriobj . path :
2014-12-09 17:20:18 +03:00
if self . source_host_transport :
2015-09-06 17:36:17 +03:00
self . source_host_socket = uriobj . path
2014-12-09 17:20:18 +03:00
else :
2015-09-06 17:36:17 +03:00
self . source_name = uriobj . path
2014-12-10 02:23:22 +03:00
if self . source_name . startswith ( " / " ) :
self . source_name = self . source_name [ 1 : ]
def _set_source_network_from_storage ( self , volxml , poolxml ) :
self . source_protocol = poolxml . type
2018-01-26 13:27:20 +03:00
logging . debug ( " disk.set_vol_object: poolxml= \n %s " ,
dir ( poolxml ) )
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
if poolxml . hosts :
self . source_host_name = poolxml . hosts [ 0 ] . name
self . source_host_port = poolxml . hosts [ 0 ] . port
2018-01-26 13:27:20 +03:00
for host in poolxml . hosts :
2018-02-08 01:27:56 +03:00
obj = self . hosts . add_new ( )
obj . name = host . name
obj . port = host . port
2014-12-10 02:23:22 +03:00
path = " "
if poolxml . source_name :
2015-01-27 16:10:12 +03:00
path + = poolxml . source_name
if poolxml . source_path :
path + = poolxml . source_path
if not path . endswith ( ' / ' ) :
path + = " / "
2014-12-10 02:23:22 +03:00
path + = volxml . name
self . source_name = path
2014-12-10 20:55:08 +03:00
self . type = " network "
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 ( ) :
self . _set_source_network_from_url ( self . _storage_backend . get_path ( ) )
2014-12-09 17:20:18 +03:00
def _build_url_from_network_source ( self ) :
2014-12-07 02:33:11 +03:00
ret = self . source_protocol
if self . source_host_transport :
ret + = " + %s " % self . source_host_transport
ret + = " :// "
if self . source_host_name :
ret + = self . source_host_name
if self . source_host_port :
ret + = " : " + str ( self . source_host_port )
if self . source_name :
if not self . source_name . startswith ( " / " ) :
ret + = " / "
ret + = self . source_name
elif self . source_host_socket :
if not self . source_host_socket . startswith ( " / " ) :
ret + = " / "
ret + = self . source_host_socket
return ret
2014-12-09 01:28:29 +03:00
def _get_default_type ( self ) :
2014-12-07 00:22:00 +03:00
if self . source_pool or self . source_volume :
2018-03-20 19:18:35 +03:00
return DeviceDisk . TYPE_VOLUME
2014-12-10 00:57:32 +03:00
if self . _storage_backend :
return self . _storage_backend . get_dev_type ( )
2014-12-07 02:33:11 +03:00
if self . source_protocol :
2018-03-20 19:18:35 +03:00
return DeviceDisk . TYPE_NETWORK
2014-12-10 00:57:32 +03:00
return self . TYPE_FILE
2014-12-07 00:22:00 +03:00
def _clear_source_xml ( self ) :
"""
Unset all XML properties that describe the actual source media
"""
self . _source_file = None
self . _source_dev = None
self . _source_dir = None
self . source_volume = None
self . source_pool = None
2014-12-07 02:33:11 +03:00
self . source_name = None
self . source_protocol = None
self . source_host_name = None
self . source_host_port = None
self . source_host_transport = None
self . source_host_socket = None
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 :
2014-12-04 21:51:23 +03:00
return " _source_dev "
2018-03-20 19:18:35 +03:00
elif disk_type == DeviceDisk . TYPE_DIR :
2014-12-04 21:51:23 +03:00
return " _source_dir "
2018-03-20 19:18:35 +03:00
elif disk_type == DeviceDisk . TYPE_FILE :
2014-12-04 21:51:23 +03:00
return " _source_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 ) :
2014-12-07 01:29:05 +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
return None
2014-12-04 21:51:23 +03:00
def _set_xmlpath ( self , val ) :
2014-12-07 00:22:00 +03:00
self . _clear_source_xml ( )
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
2014-12-04 21:51:23 +03:00
return setattr ( self , propname , val )
2013-07-14 05:04:27 +04:00
2014-12-07 00:22:00 +03:00
##################
# XML properties #
##################
2014-12-05 00:30:18 +03:00
2018-09-02 05:16:26 +03:00
# type, device, driver_name, driver_type handling
# These are all weirdly intertwined so require some special handling
def _get_type ( self ) :
if self . _type :
return self . _type
return self . _get_default_type ( )
def _set_type ( self , val ) :
self . _type = val
type = property ( _get_type , _set_type )
_type = XMLProperty ( " ./@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 )
_device = XMLProperty ( " ./@device " )
driver_name = XMLProperty ( " ./driver/@name " )
driver_type = XMLProperty ( " ./driver/@type " )
2017-09-06 10:13:44 +03:00
snapshot_policy = XMLProperty ( " ./@snapshot " )
2013-07-15 02:00:43 +04:00
2018-06-07 17:45:46 +03:00
driver_copy_on_read = XMLProperty ( " ./driver/@copy_on_read " , is_onoff = True )
2015-03-13 16:38:20 +03:00
sgio = XMLProperty ( " ./@sgio " )
2013-07-15 01:12:47 +04:00
2013-09-19 21:27:30 +04:00
bus = XMLProperty ( " ./target/@bus " )
target = XMLProperty ( " ./target/@dev " )
2013-10-04 05:38:58 +04:00
removable = XMLProperty ( " ./target/@removable " , is_onoff = True )
2013-07-14 05:04:27 +04:00
2013-09-19 21:27:30 +04:00
read_only = XMLProperty ( " ./readonly " , is_bool = True )
shareable = XMLProperty ( " ./shareable " , is_bool = True )
driver_cache = XMLProperty ( " ./driver/@cache " )
2014-04-29 18:29:33 +04:00
driver_discard = XMLProperty ( " ./driver/@discard " )
2017-07-15 20:38:39 +03:00
driver_detect_zeroes = XMLProperty ( " ./driver/@detect_zeroes " )
2013-09-19 21:27:30 +04:00
driver_io = XMLProperty ( " ./driver/@io " )
2013-03-18 01:06:52 +04:00
2013-09-19 21:27:30 +04:00
error_policy = XMLProperty ( " ./driver/@error_policy " )
serial = XMLProperty ( " ./serial " )
2014-12-07 00:22:00 +03:00
startup_policy = XMLProperty ( " ./source/@startupPolicy " )
2017-06-16 20:27:38 +03:00
logical_block_size = XMLProperty ( " ./blockio/@logical_block_size " )
physical_block_size = XMLProperty ( " ./blockio/@physical_block_size " )
2013-04-13 22:34:52 +04:00
2013-09-19 21:27:30 +04:00
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 )
2013-03-18 01:06:52 +04:00
2016-06-12 18:09:15 +03:00
seclabels = XMLChildProperty ( _DiskSeclabel , relative_xpath = " ./source " )
2015-09-05 21:40:45 +03:00
2018-06-07 18:29:43 +03:00
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 " )
2018-10-10 12:50:33 +03:00
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-03-18 01:06:52 +04:00
2013-07-13 18:09:00 +04:00
#################################
# Validation assistance methods #
#################################
2013-03-18 01:06:52 +04:00
2014-12-09 02:18:59 +03:00
def _set_default_storage_backend ( self ) :
2014-12-07 00:22:00 +03:00
path = None
vol_object = None
parent_pool = None
2017-04-27 22:44:46 +03:00
self . _source_volume_err = None
2014-12-09 01:28:29 +03:00
typ = self . _get_default_type ( )
2014-12-07 00:22:00 +03:00
2018-03-20 19:18:35 +03:00
if self . type == DeviceDisk . TYPE_NETWORK :
2014-12-09 01:28:29 +03:00
# Fill in a completed URL for virt-manager UI, path comparison, etc
2014-12-09 17:20:18 +03:00
path = self . _build_url_from_network_source ( )
2014-12-09 01:28:29 +03:00
2018-03-20 19:18:35 +03:00
if typ == DeviceDisk . TYPE_VOLUME :
2014-12-07 00:22:00 +03:00
conn = self . conn
2014-12-10 21:11:18 +03:00
if " weakref " in str ( type ( conn ) ) :
2014-12-07 00:22:00 +03:00
conn = conn ( )
try :
parent_pool = conn . storagePoolLookupByName ( self . source_pool )
vol_object = parent_pool . storageVolLookupByName (
self . source_volume )
2017-05-05 19:47:21 +03:00
except Exception as e :
2017-04-27 22:44:46 +03:00
self . _source_volume_err = str ( e )
2014-12-07 00:22:00 +03:00
logging . debug ( " Error fetching source pool= %s vol= %s " ,
self . source_pool , self . source_volume , exc_info = True )
2014-12-09 01:28:29 +03:00
if vol_object is None and path is None :
2014-12-07 00:22:00 +03:00
path = self . _get_xmlpath ( )
2014-12-09 02:18:59 +03:00
self . _change_backend ( path , vol_object , parent_pool )
2014-12-07 00:22:00 +03: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 ,
2014-12-06 05:09:26 +03:00
self . path , disk . path , disk . get_size ( ) , sparse )
2013-07-14 05:04:27 +04:00
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 ) :
2013-08-17 22:21:30 +04:00
return self . is_floppy ( ) or self . is_cdrom ( )
2013-03-18 01:06:52 +04:00
2014-12-09 01:28:29 +03:00
def _change_backend ( self , path , vol_object , parent_pool ) :
2014-12-06 05:09:26 +03:00
backend = diskbackend . StorageBackend ( self . conn , path ,
vol_object , parent_pool )
2013-07-13 18:09:00 +04:00
self . _storage_backend = backend
2013-03-18 01:06:52 +04:00
2013-07-25 20:34:37 +04:00
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.
"""
2014-12-04 21:51:23 +03:00
path = self . _get_xmlpath ( )
2013-07-25 20:34:37 +04:00
self . type = self . _get_default_type ( )
self . driver_name = self . _get_default_driver_name ( )
self . driver_type = self . _get_default_driver_type ( )
2013-03-18 01:06:52 +04:00
2014-12-04 21:51:23 +03:00
# Need to retrigger this if self.type changed
2014-12-07 02:33:11 +03:00
if path :
self . _set_xmlpath ( path )
2014-12-04 21:51:23 +03: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 .
"""
2014-12-09 01:28:29 +03:00
return ( self . _storage_backend and
not self . _storage_backend . exists ( ) )
2013-03-18 01:06:52 +04:00
2013-07-13 18:09:00 +04:00
def validate ( self ) :
if self . path is None :
2017-04-27 22:44:46 +03:00
if self . _source_volume_err :
raise RuntimeError ( self . _source_volume_err )
2013-03-18 01:06:52 +04:00
if not self . can_be_empty ( ) :
raise ValueError ( _ ( " Device type ' %s ' requires a path " ) %
self . device )
2014-12-06 20:41:44 +03:00
return
2013-03-18 01:06:52 +04:00
2018-03-20 19:18:35 +03:00
if ( self . type == DeviceDisk . TYPE_DIR and
2014-12-06 20:41:44 +03:00
not self . is_floppy ( ) ) :
raise ValueError ( _ ( " The path ' %s ' must be a file or a "
" device, not a directory " ) % self . path )
2013-03-18 01:06:52 +04:00
2014-12-09 01:28:29 +03:00
if not self . _storage_backend :
return
2014-12-09 00:02:26 +03:00
if ( not self . _storage_backend . will_create_storage ( ) and
2014-12-06 20:41:44 +03:00
not self . _storage_backend . exists ( ) ) :
raise ValueError (
_ ( " Must specify storage creation parameters for "
" non-existent path ' %s ' . " ) % self . path )
2013-03-18 01:06:52 +04:00
2014-12-09 00:02:26 +03:00
self . _storage_backend . validate ( self )
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 .
"""
2014-12-09 00:02:26 +03:00
if not self . _storage_backend . will_create_storage ( ) :
2013-07-13 18:09:00 +04:00
return
2015-09-06 21:26:50 +03:00
meter = util . ensure_meter ( meter )
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
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
2014-01-15 02:11:51 +04:00
def is_conflict_disk ( self , conn = None ) :
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
"""
2013-07-13 18:09:00 +04:00
if not self . path :
2013-03-18 01:06:52 +04:00
return False
if not conn :
conn = self . conn
2013-07-13 18:09:00 +04:00
ret = self . path_in_use_by ( conn , self . 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
2014-01-26 06:08:11 +04:00
def get_target_prefix ( self , used_targets = None ) :
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 " )
elif self . bus or not used_targets :
# sata, scsi, usb, sd
return _return ( " sd " )
# If guest already has some disks defined
preforder = [ " vd " , " xvd " , " sd " , " hd " ]
for pref in preforder :
for target in used_targets :
if target . startswith ( pref ) :
return _return ( pref )
return _return ( " sd " )
2013-03-18 01:06:52 +04:00
2014-02-12 18:46:35 +04:00
def generate_target ( self , skip_targets , pref_ctrl = None ) :
2013-03-18 01:06:52 +04:00
"""
Generate target device ( ' hda ' , ' sdb ' , etc . . ) for disk , excluding
2014-02-12 18:46:35 +04:00
any targets in ' skip_targets ' . If given the ' pref_ctrl '
parameter , it tries to select the target so that the disk is
mapped onto that controller .
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
: param pref_ctrl : preferred controller to connect the disk to
: returns : generated target
2013-03-18 01:06:52 +04:00
"""
2014-01-26 06:08:11 +04:00
prefix , maxnode = self . get_target_prefix ( skip_targets )
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
2014-02-17 19:41:02 +04:00
ran = range ( maxnode )
if pref_ctrl is not None :
2014-02-12 18:46:35 +04:00
# We assume narrow SCSI bus and libvirt assigning 7
2014-02-17 19:41:02 +04:00
# (1-7, 8-14, etc.) devices per controller
2014-02-12 18:46:35 +04:00
ran = range ( pref_ctrl * 7 , ( pref_ctrl + 1 ) * 7 )
for i in ran :
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
2014-02-17 19:41:02 +04:00
if pref_ctrl is not None :
2014-02-12 18:46:35 +04:00
# This basically means that we either chose full
# controller or didn't add any
raise ValueError ( _ ( " Controller number %d for disk of type %s has "
" no empty slot to use " % ( pref_ctrl , prefix ) ) )
else :
2017-07-11 02:55:57 +03:00
raise ValueError ( _ ( " Only %s disks for bus ' %s ' are supported "
% ( maxnode , self . bus ) ) )
2018-09-02 18:57:41 +03:00
##################
# Default config #
##################
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 guest . os . is_arm_machvirt ( ) :
# We prefer virtio-scsi for machvirt, gets us hotplug
return " scsi "
if self . is_disk ( ) and guest . supports_virtiodisk ( ) :
return " virtio "
if guest . os . is_pseries ( ) and self . is_cdrom ( ) :
return " scsi "
if guest . os . is_arm ( ) :
return " sd "
if guest . os . is_q35 ( ) :
return " sata "
if self . is_cdrom ( ) and guest . os . is_s390x ( ) :
return " scsi "
return " ide "
def set_defaults ( self , guest ) :
2018-09-02 05:16:26 +03:00
if not self . _device :
self . _device = self . _get_device ( )
if not self . _type :
self . _type = self . _get_default_type ( )
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
if ( self . conn . is_qemu ( ) and
self . is_disk ( ) and
self . type == self . TYPE_BLOCK ) :
if not self . driver_cache :
self . driver_cache = self . CACHE_MODE_NONE
if not self . driver_io :
self . driver_io = self . IO_MODE_NATIVE
if not self . target :
used_targets = [ d . target for d in guest . devices . disk if d . target ]
self . generate_target ( used_targets )