2013-03-17 17:06:52 -04:00
#
# Classes for building disk device xml
#
2014-02-12 15:44:40 +01:00
# Copyright 2006-2008, 2012-2014 Red Hat, Inc.
2013-03-17 17:06:52 -04:00
#
2018-04-04 14:35:41 +01:00
# This work is licensed under the GNU GPLv2 or later.
2018-03-20 15:00:02 -04:00
# See the COPYING file in the top-level directory.
2013-03-17 17:06:52 -04:00
2020-01-18 15:22:50 -05:00
import os
2019-06-16 21:12:39 -04:00
from . . logger import log
2013-03-17 17:06:52 -04:00
2018-03-20 12:27:37 -04:00
from . . import diskbackend
2019-06-07 17:32:51 -04:00
from . . import progress
2020-01-28 19:03:44 -05:00
from . . import xmlutil
2019-05-13 20:03:23 -04:00
from . device import Device , DeviceSeclabel
2018-03-20 12:27:37 -04:00
from . . xmlbuilder import XMLBuilder , XMLChildProperty , XMLProperty
2013-03-17 17:06:52 -04:00
2013-04-13 14:34:52 -04:00
2018-10-11 17:46:32 -04:00
def _qemu_sanitize_drvtype ( phystype , fmt ) :
2013-03-17 17:06:52 -04:00
"""
Sanitize libvirt storage volume format to a valid qemu driver type
"""
2013-04-13 14:34:52 -04:00
raw_list = [ " iso " ]
2013-03-17 17:06:52 -04:00
2018-03-20 12:18:35 -04:00
if phystype == DeviceDisk . TYPE_BLOCK :
2018-10-11 17:46:32 -04:00
return DeviceDisk . DRIVER_TYPE_RAW
2013-03-17 17:06:52 -04:00
if fmt in raw_list :
2018-03-20 12:18:35 -04:00
return DeviceDisk . DRIVER_TYPE_RAW
2013-03-17 17:06:52 -04:00
return fmt
2013-04-13 14:34:52 -04:00
2018-01-26 12:27:20 +02:00
class _Host ( XMLBuilder ) :
2019-05-15 09:47:53 -04:00
_XML_PROP_ORDER = [ " name " , " port " , " transport " , " socket " ]
2018-03-21 10:53:34 -04:00
XML_NAME = " host "
2018-01-26 12:27:20 +02:00
name = XMLProperty ( " ./@name " )
port = XMLProperty ( " ./@port " , is_int = True )
2019-05-15 09:47:53 -04:00
transport = XMLProperty ( " ./@transport " )
socket = XMLProperty ( " ./@socket " )
2018-01-26 12:27:20 +02:00
2018-03-20 12:18:35 -04:00
class DeviceDisk ( Device ) :
2018-03-21 10:53:34 -04:00
XML_NAME = " disk "
2013-03-17 17:06:52 -04:00
2015-09-23 17:26:48 -04:00
DRIVER_NAME_PHY = " phy "
DRIVER_NAME_QEMU = " qemu "
DRIVER_TYPE_RAW = " raw "
2013-03-17 17:06:52 -04:00
CACHE_MODE_NONE = " none "
CACHE_MODE_WRITETHROUGH = " writethrough "
CACHE_MODE_WRITEBACK = " writeback "
2013-09-30 13:56:42 -04:00
CACHE_MODE_DIRECTSYNC = " directsync "
CACHE_MODE_UNSAFE = " unsafe "
2018-09-06 16:45:44 -04:00
CACHE_MODES = [ CACHE_MODE_NONE , CACHE_MODE_WRITETHROUGH ,
2013-09-30 13:56:42 -04:00
CACHE_MODE_WRITEBACK , CACHE_MODE_DIRECTSYNC , CACHE_MODE_UNSAFE ]
2013-03-17 17:06:52 -04:00
2014-04-29 10:29:33 -04:00
DISCARD_MODE_IGNORE = " ignore "
DISCARD_MODE_UNMAP = " unmap "
2018-09-06 16:45:44 -04:00
DISCARD_MODES = [ DISCARD_MODE_IGNORE , DISCARD_MODE_UNMAP ]
2014-04-29 10:29:33 -04:00
2018-09-06 16:45:44 -04: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-17 17: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 15:24:34 +02:00
TYPE_VOLUME = " volume "
2014-12-06 18:33:11 -05:00
TYPE_NETWORK = " network "
2013-03-17 17:06:52 -04:00
IO_MODE_NATIVE = " native "
2020-01-28 20:11:47 -05:00
2013-08-17 10:35:44 -04:00
2013-03-17 17:06:52 -04:00
@staticmethod
2014-12-04 20:00:34 -05:00
def path_definitely_exists ( conn , path ) :
2013-03-17 17:06:52 -04:00
"""
2014-12-04 20:00:34 -05: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 11:28:26 -04:00
may have disappeared behind our back , but that shouldn ' t have bad
2014-12-04 20:00:34 -05:00
effects in practice . )
2013-03-17 17:06:52 -04:00
"""
2018-10-11 19:20:46 -04:00
return diskbackend . path_definitely_exists ( conn , path )
2013-03-17 17:06:52 -04:00
@staticmethod
2018-10-11 18:52:45 -04:00
def check_path_search ( conn , path ) :
2013-03-17 17:06:52 -04:00
"""
2018-10-11 18:52:45 -04:00
Check if the connection DAC user has search permissions for all the
directories in the passed path .
2013-03-17 17:06:52 -04:00
2018-10-11 18:52:45 -04: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-17 17:06:52 -04:00
"""
2018-10-11 18:52:45 -04:00
class SearchData ( object ) :
def __init__ ( self ) :
self . user = None
self . uid = None
self . fixlist = [ ]
searchdata = SearchData ( )
2013-03-17 17:06:52 -04:00
if path is None :
2018-10-11 18:52:45 -04:00
return searchdata
2020-01-18 15:22:50 -05:00
2013-07-05 20:36:28 -04:00
if conn . is_remote ( ) :
2018-10-11 18:52:45 -04:00
return searchdata
2020-02-02 19:43:06 -05:00
if not conn . is_qemu_privileged ( ) :
2018-10-11 18:52:45 -04:00
return searchdata
2014-12-09 09:20:18 -05:00
if diskbackend . path_is_url ( path ) :
2018-10-11 18:52:45 -04:00
return searchdata
2018-10-09 14:30:09 -06:00
if diskbackend . path_is_network_vol ( conn , path ) :
return searchdata
2020-01-28 19:03:44 -05:00
path = os . path . abspath ( path )
2013-03-17 17:06:52 -04:00
2018-10-11 18:52:45 -04:00
user , uid = conn . caps . host . get_qemu_baselabel ( )
if not user :
2020-01-30 16:17:42 -05:00
return searchdata
2018-10-11 18:52:45 -04:00
if uid == 0 :
return searchdata
2013-03-17 17:06:52 -04:00
2018-10-11 18:52:45 -04:00
searchdata . user = user
searchdata . uid = uid
searchdata . fixlist = diskbackend . is_path_searchable ( path , uid , user )
searchdata . fixlist . reverse ( )
return searchdata
2014-02-10 18:08:59 -05:00
2013-03-17 17:06:52 -04:00
@staticmethod
2020-01-28 19:03:44 -05:00
def fix_path_search ( searchdata ) :
2013-03-17 17:06:52 -04:00
"""
2018-10-11 18:52:45 -04:00
Try to fix any permission problems found by check_path_search
2013-03-17 17:06:52 -04:00
2018-02-14 07:17:31 -05:00
: returns : Return a dictionary of entries { broken path : error msg }
2013-03-17 17:06:52 -04:00
"""
2018-10-11 18:52:45 -04:00
errdict = diskbackend . set_dirs_searchable (
2018-10-12 12:58:51 -04:00
searchdata . fixlist , searchdata . user )
2013-03-17 17:06:52 -04:00
return errdict
@staticmethod
2014-01-14 16:32:05 -05:00
def path_in_use_by ( conn , path , shareable = False , read_only = False ) :
2013-03-17 17:06:52 -04:00
"""
Return a list of VM names that are using the passed path .
2018-02-14 07:17:31 -05: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-14 16:32:05 -05:00
don ' t warn if it conflicts with another shareable source.
2018-02-14 07:17:31 -05:00
: param read_only : Path we are checking is marked read_only , so
2014-01-14 16:32:05 -05:00
don ' t warn if it conflicts with another read_only source.
2013-03-17 17:06:52 -04:00
"""
if not path :
2014-01-14 16:32:05 -05:00
return [ ]
2013-09-28 20:05:13 -04:00
2014-01-14 17:44:14 -05:00
# Find all volumes that have 'path' somewhere in their backing chain
2013-09-28 20:05:13 -04:00
vols = [ ]
2014-01-14 17:44:14 -05: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 :
2020-01-28 19:03:44 -05:00
break # pragma: no cover
2014-01-14 17:44:14 -05:00
backpath = vol . target_path
vols . append ( backpath )
2013-03-17 17:06:52 -04:00
2014-01-14 16:32:05 -05:00
ret = [ ]
2018-08-31 15:20:50 -04:00
vms = conn . fetch_all_domains ( )
2013-03-17 17:06:52 -04:00
for vm in vms :
2014-01-14 16:32:05 -05:00
if not read_only :
if path in [ vm . os . kernel , vm . os . initrd , vm . os . dtb ] :
ret . append ( vm . name )
continue
2013-09-28 20:05:13 -04:00
2018-03-20 17:23:34 -04:00
for disk in vm . devices . disk :
2013-09-28 20:05:13 -04:00
if disk . path in vols and vm . name not in ret :
2014-01-14 16:32:05 -05:00
# VM uses the path indirectly via backing store
2013-09-28 20:05:13 -04:00
ret . append ( vm . name )
2014-01-14 16:32:05 -05: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-28 20:05:13 -04:00
return ret
2013-03-17 17:06:52 -04:00
2013-07-25 16:21:30 -04:00
@staticmethod
2014-12-05 21:09:26 -05:00
def build_vol_install ( conn , volname , poolobj , size , sparse ,
2016-06-16 20:08:53 -04:00
fmt = None , backing_store = None , backing_format = None ) :
2014-12-05 21:09:26 -05:00
"""
2018-03-20 12:18:35 -04:00
Helper for building a StorageVolume instance to pass to DeviceDisk
2014-12-05 21:09:26 -05:00
for eventual storage creation .
: param volname : name of the volume to be created
: param size : size in bytes
"""
2018-03-20 12:27:37 -04:00
from . . storage import StorageVolume
2014-12-05 21:09:26 -05:00
if size is None :
raise ValueError ( _ ( " Size must be specified for non "
" existent volume ' %s ' " % volname ) )
2014-12-06 13:04:59 -05: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-16 21:12:39 -04:00
log . debug ( " Creating volume ' %s ' on pool ' %s ' " ,
2014-12-05 21:09:26 -05: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-16 20:08:53 -04:00
volinst . backing_format = backing_format
2014-12-05 21:09:26 -05:00
if fmt :
2019-07-02 16:56:35 -04:00
if not volinst . supports_format ( ) :
2014-12-05 21:09:26 -05:00
raise ValueError ( _ ( " Format attribute not supported for this "
" volume type " ) )
volinst . format = fmt
return volinst
2013-07-15 09:49:46 -04:00
2013-10-03 07: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 12:35:55 +01:00
amt = ( num % ( 26 * * ( factor + 1 ) ) ) / / ( 26 * * factor )
2013-10-03 07: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-28 19:46:16 -05:00
continue
2013-10-03 07:20:54 -04:00
gen_t + = " %c " % ( ord ( ' a ' ) + digit - 1 )
return gen_t
2013-07-15 09:49:46 -04:00
2014-02-12 15:44:40 +01:00
@staticmethod
def target_to_num ( tgt ) :
"""
Convert disk / dev number ( like hda , hdb , hdaa , etc . ) to an index
"""
num = 0
2014-02-23 13:31:48 +08:00
k = 0
2014-02-12 15:44:40 +01: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 13:31:48 +08:00
if i != 0 :
k = 1
num + = ( ord ( c ) - ord ( ' a ' ) + k ) * ( 26 * * i )
2014-02-12 15:44:40 +01:00
return num
2013-07-16 12:48:52 -04:00
_XML_PROP_ORDER = [
2020-01-29 06:18:15 -05:00
" _xmltype " , " _device " , " snapshot_policy " ,
2013-07-16 12:48:52 -04:00
" driver_name " , " driver_type " ,
2017-07-15 13:38:39 -04:00
" driver_cache " , " driver_discard " , " driver_detect_zeroes " ,
" driver_io " , " error_policy " ,
2014-12-06 16:22:00 -05:00
" _source_file " , " _source_dev " , " _source_dir " ,
2018-01-26 12:27:20 +02:00
" auth_username " , " auth_secret_type " , " auth_secret_uuid " ,
2014-12-06 18:33:11 -05:00
" source_volume " , " source_pool " , " source_protocol " , " source_name " ,
" source_host_name " , " source_host_port " ,
" source_host_transport " , " source_host_socket " ,
2014-12-04 13:51:23 -05:00
" target " , " bus " ,
2013-07-16 12:48:52 -04:00
]
2013-07-14 15:06:40 -04:00
2013-09-11 11:47:09 -04:00
def __init__ ( self , * args , * * kwargs ) :
2018-03-20 12:18:35 -04:00
Device . __init__ ( self , * args , * * kwargs )
2013-03-17 17:06:52 -04:00
2017-04-27 15:44:46 -04:00
self . _source_volume_err = None
2015-09-06 13:42:07 -04:00
self . storage_was_created = False
2013-07-13 10:09:00 -04:00
2020-01-29 06:18:15 -05:00
self . _storage_backend = diskbackend . StorageBackendStub (
self . conn , self . _get_xmlpath ( ) , self . _xmltype , self . driver_type )
2013-03-17 17:06:52 -04:00
2013-07-14 18:14:55 -04:00
#############################
# Public property-esque API #
#############################
2013-03-17 17:06:52 -04:00
def _get_path ( self ) :
2020-01-29 06:18:15 -05:00
if ( self . _storage_backend . is_stub ( ) and not
self . _storage_backend . get_path ( ) ) :
self . _resolve_storage_backend ( )
2014-12-09 16:57:32 -05:00
return self . _storage_backend . get_path ( )
2014-12-09 09:20:18 -05:00
def _set_path ( self , newpath ) :
2020-01-29 06:18:15 -05:00
if self . _storage_backend . will_create_storage ( ) :
2020-01-28 19:03:44 -05:00
xmlutil . raise_programming_error ( None ,
" Can ' t change disk path if storage creation info "
" has been set. " )
2014-12-08 17:28:29 -05:00
2014-12-10 09:04:09 -05: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-08 17:28:29 -05:00
2014-12-09 09:20:18 -05:00
self . _change_backend ( newpath , vol_object , parent_pool )
2014-12-04 13:51:23 -05:00
self . _set_xmlpath ( self . path )
2013-07-14 18:14:55 -04:00
path = property ( _get_path , _set_path )
2019-05-23 18:13:24 -04:00
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
2020-01-29 06:18:15 -05:00
if self . _storage_backend . is_stub ( ) :
self . _resolve_storage_backend ( )
2019-05-23 18:13:24 -04:00
2014-12-09 17:03:48 -05:00
def set_vol_object ( self , vol_object , parent_pool ) :
2019-06-16 21:12:39 -04:00
log . debug ( " disk.set_vol_object: volxml= \n %s " ,
2015-01-29 10:20:18 -05:00
vol_object . XMLDesc ( 0 ) )
2019-06-16 21:12:39 -04:00
log . debug ( " disk.set_vol_object: poolxml= \n %s " ,
2015-01-29 10:20:18 -05:00
parent_pool . XMLDesc ( 0 ) )
2014-12-09 17:03:48 -05:00
self . _change_backend ( None , vol_object , parent_pool )
2014-12-05 18:17:39 -05:00
self . _set_xmlpath ( self . path )
2013-03-17 17:06:52 -04:00
2014-12-05 20:12:35 -05:00
def set_vol_install ( self , vol_install ) :
2019-06-16 21:12:39 -04:00
log . debug ( " disk.set_vol_install: name= %s poolxml= \n %s " ,
2015-01-29 10:20:18 -05:00
vol_install . name , vol_install . pool . XMLDesc ( 0 ) )
2014-12-08 16:02:26 -05:00
self . _storage_backend = diskbackend . ManagedStorageCreator (
2014-12-05 21:09:26 -05:00
self . conn , vol_install )
2014-12-05 20:12:35 -05:00
self . _set_xmlpath ( self . path )
2013-07-14 18:14:55 -04:00
def get_vol_object ( self ) :
return self . _storage_backend . get_vol_object ( )
def get_vol_install ( self ) :
2014-12-08 16:02:26 -05:00
return self . _storage_backend . get_vol_install ( )
2014-12-05 19:52:25 -05:00
def get_parent_pool ( self ) :
return self . _storage_backend . get_parent_pool ( )
2013-07-14 18:14:55 -04:00
def get_size ( self ) :
return self . _storage_backend . get_size ( )
2013-03-17 17:06:52 -04:00
2013-07-14 17:12:47 -04:00
2013-07-14 17:54:01 -04:00
#############################
# Internal defaults helpers #
#############################
2013-07-14 18:00:43 -04:00
def _get_default_driver_name ( self ) :
2013-07-17 17:58:24 -04:00
if not self . path :
return None
2015-09-23 17:26:48 -04: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
2019-06-07 16:06:52 -04:00
if self . conn . support . conn_disk_driver_name_qemu ( ) :
2015-09-23 17:26:48 -04:00
return self . DRIVER_NAME_QEMU
2013-07-14 18: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 13:56:53 +01:00
https : / / lists . gnu . org / archive / html / qemu - devel / 2008 - 04 / msg00675 . html
2013-07-14 18:00:43 -04:00
"""
2015-09-23 17:26:48 -04:00
if self . driver_name != self . DRIVER_NAME_QEMU :
2013-07-14 18:00:43 -04:00
return None
2014-12-08 16:02:26 -05:00
drvtype = self . _storage_backend . get_driver_type ( )
2013-07-14 18:00:43 -04:00
return _qemu_sanitize_drvtype ( self . type , drvtype )
2013-07-14 17:12:47 -04:00
2014-12-06 16:22:00 -05:00
#############################
# XML source media handling #
#############################
2013-07-14 18:14:55 -04:00
2014-12-04 13:51:23 -05:00
_source_file = XMLProperty ( " ./source/@file " )
_source_dev = XMLProperty ( " ./source/@dev " )
_source_dir = XMLProperty ( " ./source/@dir " )
2014-12-06 16:22:00 -05:00
source_pool = XMLProperty ( " ./source/@pool " )
source_volume = XMLProperty ( " ./source/@volume " )
2018-01-26 12:27:20 +02: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-06 18:33:11 -05: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-09 18:23:22 -05:00
def _set_source_network_from_url ( self , uri ) :
2018-03-20 12:27:37 -04:00
from . . uri import URI
2015-09-06 10:36:17 -04: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 09:20:18 -05:00
if self . source_host_transport :
2015-09-06 10:36:17 -04:00
self . source_host_socket = uriobj . path
2014-12-09 09:20:18 -05:00
else :
2015-09-06 10:36:17 -04:00
self . source_name = uriobj . path
2014-12-09 18:23:22 -05:00
if self . source_name . startswith ( " / " ) :
self . source_name = self . source_name [ 1 : ]
def _set_source_network_from_storage ( self , volxml , poolxml ) :
2019-06-14 20:35:41 -04:00
is_iscsi_direct = poolxml . type == " iscsi-direct "
protocol = poolxml . type
if is_iscsi_direct :
protocol = " iscsi "
self . source_protocol = protocol
2018-01-26 12:27:20 +02: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-09 18:23:22 -05:00
if poolxml . hosts :
self . source_host_name = poolxml . hosts [ 0 ] . name
self . source_host_port = poolxml . hosts [ 0 ] . port
2018-01-26 12:27:20 +02:00
for host in poolxml . hosts :
2018-02-07 17:27:56 -05:00
obj = self . hosts . add_new ( )
obj . name = host . name
obj . port = host . port
2014-12-09 18:23:22 -05:00
path = " "
2019-06-14 20:35:41 -04:00
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 . source_name = path or None
2014-12-10 12:55:08 -05:00
self . type = " network "
2014-12-09 18:23:22 -05: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 09:20:18 -05:00
def _build_url_from_network_source ( self ) :
2019-06-16 11:01:47 -04:00
ret = self . source_protocol or " unknown "
2014-12-06 18:33:11 -05:00
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-08 17:28:29 -05:00
def _get_default_type ( self ) :
2014-12-06 16:22:00 -05:00
if self . source_pool or self . source_volume :
2018-03-20 12:18:35 -04:00
return DeviceDisk . TYPE_VOLUME
2020-01-29 06:18:15 -05:00
if not self . _storage_backend . is_stub ( ) :
2014-12-09 16:57:32 -05:00
return self . _storage_backend . get_dev_type ( )
2014-12-06 18:33:11 -05:00
if self . source_protocol :
2018-03-20 12:18:35 -04:00
return DeviceDisk . TYPE_NETWORK
2014-12-09 16:57:32 -05:00
return self . TYPE_FILE
2014-12-06 16:22:00 -05: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-06 18:33:11 -05: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 13:51:23 -05:00
def _disk_type_to_object_prop_name ( self ) :
disk_type = self . type
2018-03-20 12:18:35 -04:00
if disk_type == DeviceDisk . TYPE_BLOCK :
2014-12-04 13:51:23 -05:00
return " _source_dev "
2018-03-20 12:18:35 -04:00
elif disk_type == DeviceDisk . TYPE_DIR :
2014-12-04 13:51:23 -05:00
return " _source_dir "
2018-03-20 12:18:35 -04:00
elif disk_type == DeviceDisk . TYPE_FILE :
2014-12-04 13:51:23 -05:00
return " _source_file "
2014-12-06 16:22:00 -05:00
return None
2014-12-07 10:08:33 -05: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 13:51:23 -05:00
def _get_xmlpath ( self ) :
2014-12-06 17:29:05 -05: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 13:51:23 -05:00
def _set_xmlpath ( self , val ) :
2014-12-06 16:22:00 -05:00
self . _clear_source_xml ( )
2014-12-04 13:51:23 -05:00
2014-12-09 16:57:32 -05:00
if self . _storage_backend . get_dev_type ( ) == " network " :
2014-12-09 18:23:22 -05:00
self . _set_network_source_from_backend ( )
2014-12-09 09:20:18 -05:00
return
2014-12-04 13:51:23 -05:00
propname = self . _disk_type_to_object_prop_name ( )
2014-12-06 16:22:00 -05:00
if not propname :
return
2014-12-04 13:51:23 -05:00
return setattr ( self , propname , val )
2013-07-13 21:04:27 -04:00
2014-12-06 16:22:00 -05:00
##################
# XML properties #
##################
2014-12-04 16:30:18 -05:00
2018-09-01 22:16:26 -04:00
# type, device, driver_name, driver_type handling
# These are all weirdly intertwined so require some special handling
def _get_type ( self ) :
2020-01-29 06:18:15 -05:00
if self . _xmltype :
return self . _xmltype
2018-09-01 22:16:26 -04:00
return self . _get_default_type ( )
def _set_type ( self , val ) :
2020-01-29 06:18:15 -05:00
self . _xmltype = val
2018-09-01 22:16:26 -04:00
type = property ( _get_type , _set_type )
2020-01-29 06:18:15 -05:00
_xmltype = XMLProperty ( " ./@type " )
2018-09-01 22:16:26 -04:00
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 09:13:44 +02:00
snapshot_policy = XMLProperty ( " ./@snapshot " )
2013-07-14 18:00:43 -04:00
2018-06-07 10:45:46 -04:00
driver_copy_on_read = XMLProperty ( " ./driver/@copy_on_read " , is_onoff = True )
2015-03-13 14:38:20 +01:00
sgio = XMLProperty ( " ./@sgio " )
2019-05-20 14:43:34 +03:00
rawio = XMLProperty ( " ./@rawio " )
2013-07-14 17:12:47 -04:00
2013-09-19 13:27:30 -04:00
bus = XMLProperty ( " ./target/@bus " )
target = XMLProperty ( " ./target/@dev " )
2013-10-04 03:38:58 +02:00
removable = XMLProperty ( " ./target/@removable " , is_onoff = True )
2013-07-13 21:04:27 -04:00
2013-09-19 13: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 10:29:33 -04:00
driver_discard = XMLProperty ( " ./driver/@discard " )
2017-07-15 13:38:39 -04:00
driver_detect_zeroes = XMLProperty ( " ./driver/@detect_zeroes " )
2013-09-19 13:27:30 -04:00
driver_io = XMLProperty ( " ./driver/@io " )
2019-08-28 12:43:56 -04:00
driver_iothread = XMLProperty ( " ./driver/@iothread " , is_int = True )
2013-03-17 17:06:52 -04:00
2013-09-19 13:27:30 -04:00
error_policy = XMLProperty ( " ./driver/@error_policy " )
serial = XMLProperty ( " ./serial " )
2019-05-17 18:26:02 +03:00
wwn = XMLProperty ( " ./wwn " )
2014-12-06 16:22:00 -05:00
startup_policy = XMLProperty ( " ./source/@startupPolicy " )
2017-06-16 13:27:38 -04:00
logical_block_size = XMLProperty ( " ./blockio/@logical_block_size " )
physical_block_size = XMLProperty ( " ./blockio/@physical_block_size " )
2013-04-13 14:34:52 -04:00
2013-09-19 13: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-17 17:06:52 -04:00
2019-05-13 20:03:23 -04:00
seclabels = XMLChildProperty ( DeviceSeclabel , relative_xpath = " ./source " )
2015-09-05 14:40:45 -04:00
2018-06-07 11:29:43 -04: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 17:50:33 +08: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-17 17:06:52 -04:00
2013-07-13 10:09:00 -04:00
#################################
# Validation assistance methods #
#################################
2013-03-17 17:06:52 -04:00
2020-01-29 06:18:15 -05:00
def _resolve_storage_backend ( self ) :
2014-12-06 16:22:00 -05:00
path = None
vol_object = None
parent_pool = None
2017-04-27 15:44:46 -04:00
self . _source_volume_err = None
2014-12-08 17:28:29 -05:00
typ = self . _get_default_type ( )
2014-12-06 16:22:00 -05:00
2018-03-20 12:18:35 -04:00
if self . type == DeviceDisk . TYPE_NETWORK :
2014-12-08 17:28:29 -05:00
# Fill in a completed URL for virt-manager UI, path comparison, etc
2014-12-09 09:20:18 -05:00
path = self . _build_url_from_network_source ( )
2014-12-08 17:28:29 -05:00
2018-03-20 12:18:35 -04:00
if typ == DeviceDisk . TYPE_VOLUME :
2014-12-06 16:22:00 -05:00
try :
2019-06-07 15:53:36 -04:00
parent_pool = self . conn . storagePoolLookupByName (
self . source_pool )
2014-12-06 16:22:00 -05:00
vol_object = parent_pool . storageVolLookupByName (
self . source_volume )
2017-05-05 12:47:21 -04:00
except Exception as e :
2017-04-27 15:44:46 -04:00
self . _source_volume_err = str ( e )
2019-06-16 21:12:39 -04:00
log . debug ( " Error fetching source pool= %s vol= %s " ,
2014-12-06 16:22:00 -05:00
self . source_pool , self . source_volume , exc_info = True )
2014-12-08 17:28:29 -05:00
if vol_object is None and path is None :
2014-12-06 16:22:00 -05:00
path = self . _get_xmlpath ( )
2014-12-08 18:18:59 -05:00
self . _change_backend ( path , vol_object , parent_pool )
2014-12-06 16:22:00 -05:00
2014-12-05 21:09:26 -05:00
def set_local_disk_to_clone ( self , disk , sparse ) :
2013-07-13 21:04:27 -04:00
"""
2014-12-05 21:09:26 -05:00
Set a path to manually clone ( as in , not through libvirt )
2013-07-13 21:04:27 -04:00
"""
2014-12-08 16:02:26 -05:00
self . _storage_backend = diskbackend . CloneStorageCreator ( self . conn ,
2014-12-05 21:09:26 -05:00
self . path , disk . path , disk . get_size ( ) , sparse )
2013-07-13 21:04:27 -04:00
2013-08-17 14: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-13 21:04:27 -04:00
2013-03-17 17:06:52 -04:00
def can_be_empty ( self ) :
2013-08-17 14:21:30 -04:00
return self . is_floppy ( ) or self . is_cdrom ( )
2013-03-17 17:06:52 -04:00
2014-12-08 17:28:29 -05:00
def _change_backend ( self , path , vol_object , parent_pool ) :
2014-12-05 21:09:26 -05:00
backend = diskbackend . StorageBackend ( self . conn , path ,
vol_object , parent_pool )
2013-07-13 10:09:00 -04:00
self . _storage_backend = backend
2013-03-17 17:06:52 -04:00
2013-07-25 12: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 13:51:23 -05:00
path = self . _get_xmlpath ( )
2013-07-25 12: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-17 17:06:52 -04:00
2014-12-04 13:51:23 -05:00
# Need to retrigger this if self.type changed
2014-12-06 18:33:11 -05:00
if path :
self . _set_xmlpath ( path )
2014-12-04 13:51:23 -05:00
2014-12-05 20:25:11 -05:00
def wants_storage_creation ( self ) :
"""
If true , this disk needs storage creation parameters or things
will error .
"""
2020-01-29 06:18:15 -05:00
return not self . _storage_backend . exists ( )
2013-03-17 17:06:52 -04:00
2013-07-13 10:09:00 -04:00
def validate ( self ) :
if self . path is None :
2017-04-27 15:44:46 -04:00
if self . _source_volume_err :
raise RuntimeError ( self . _source_volume_err )
2013-03-17 17:06:52 -04:00
if not self . can_be_empty ( ) :
raise ValueError ( _ ( " Device type ' %s ' requires a path " ) %
self . device )
2014-12-06 12:41:44 -05:00
return
2013-03-17 17:06:52 -04:00
2020-01-29 07:30:51 -05:00
if ( not self . _storage_backend . exists ( ) and
not self . _storage_backend . will_create_storage ( ) ) :
2014-12-06 12:41:44 -05:00
raise ValueError (
_ ( " Must specify storage creation parameters for "
" non-existent path ' %s ' . " ) % self . path )
2013-03-17 17:06:52 -04:00
2020-01-29 07:30:51 -05:00
self . _storage_backend . validate ( )
2013-03-17 17:06:52 -04:00
2018-09-03 16:44:38 -04:00
def build_storage ( self , meter ) :
2013-03-17 17: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 06:18:15 -05:00
if not self . _storage_backend . will_create_storage ( ) :
2013-07-13 10:09:00 -04:00
return
2019-06-07 17:32:51 -04:00
meter = progress . ensure_meter ( meter )
2020-07-04 18:02:00 -04:00
# pylint: disable=assignment-from-no-return
2014-12-09 17:03:48 -05:00
vol_object = self . _storage_backend . create ( meter )
2015-09-06 13:42:07 -04:00
self . storage_was_created = True
2014-12-09 17:03:48 -05:00
if not vol_object :
return
parent_pool = self . get_vol_install ( ) . pool
self . _change_backend ( None , vol_object , parent_pool )
2013-03-17 17: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-08 16:02:26 -05:00
return self . _storage_backend . is_size_conflict ( )
2013-03-17 17:06:52 -04:00
2020-01-28 19:03:44 -05:00
def is_conflict_disk ( self ) :
2013-03-17 17:06:52 -04:00
"""
check if specified storage is in use by any other VMs on passed
connection .
2018-02-14 07:17:31 -05:00
: returns : list of colliding VM names
2013-03-17 17:06:52 -04:00
"""
2020-01-28 19:03:44 -05:00
ret = self . path_in_use_by ( self . conn , self . path ,
2014-01-14 16:32:05 -05:00
shareable = self . shareable ,
read_only = self . read_only )
2013-03-17 17:06:52 -04:00
return ret
2020-01-29 05:49:13 -05:00
def get_target_prefix ( self ) :
2013-03-17 17:06:52 -04:00
"""
Returns the suggested disk target prefix ( hd , xvd , sd . . . ) for the
disk .
2018-02-14 07:17:31 -05:00
: returns : str prefix , or None if no reasonable guess can be made
2013-03-17 17:06:52 -04:00
"""
2018-05-21 20:42:50 +01:00
# The upper limits here aren't necessarily 1024, but let the HV
2013-03-17 17:06:52 -04:00
# error as appropriate.
2014-01-25 21:08:11 -05:00
def _return ( prefix ) :
nummap = {
" vd " : 1024 ,
" xvd " : 1024 ,
" fd " : 2 ,
" hd " : 4 ,
" sd " : 1024 ,
}
return prefix , nummap [ prefix ]
2013-03-17 17:06:52 -04:00
if self . bus == " virtio " :
2014-01-25 21:08:11 -05:00
return _return ( " vd " )
2013-03-17 17:06:52 -04:00
elif self . bus == " xen " :
2014-01-25 21:08:11 -05:00
return _return ( " xvd " )
2013-08-17 14:21:30 -04:00
elif self . bus == " fdc " or self . is_floppy ( ) :
2014-01-25 21:08:11 -05:00
return _return ( " fd " )
2013-03-17 17:06:52 -04:00
elif self . bus == " ide " :
2014-01-25 21:08:11 -05:00
return _return ( " hd " )
2020-01-29 05:49:13 -05:00
# sata, scsi, usb, sd
2014-01-25 21:08:11 -05:00
return _return ( " sd " )
2013-03-17 17:06:52 -04:00
2020-01-29 05:49:13 -05:00
2020-01-29 05:44:49 -05:00
def generate_target ( self , skip_targets ) :
2013-03-17 17:06:52 -04:00
"""
Generate target device ( ' hda ' , ' sdb ' , etc . . ) for disk , excluding
2020-01-29 05:44:49 -05:00
any targets in ' skip_targets ' .
2014-02-12 15:46:35 +01:00
Sets self . target , and returns the generated value .
2013-03-17 17:06:52 -04:00
2018-02-14 07:17:31 -05:00
: param skip_targets : list of targets to exclude
: returns : generated target
2013-03-17 17:06:52 -04:00
"""
2020-01-29 05:49:13 -05:00
prefix , maxnode = self . get_target_prefix ( )
2013-10-03 08:40:24 -04:00
skip_targets = [ t for t in skip_targets if t and t . startswith ( prefix ) ]
skip_targets . sort ( )
2013-03-17 17:06:52 -04:00
2013-10-03 08:40:24 -04:00
def get_target ( ) :
first_found = None
2013-03-17 17:06:52 -04:00
2020-01-29 05:44:49 -05:00
for i in range ( maxnode ) :
2014-02-17 16:41:02 +01:00
gen_t = prefix + self . num_to_target ( i + 1 )
2013-10-03 08: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 15:46:35 +01:00
2020-07-11 17:31:40 -04:00
raise ValueError (
_ ( " Only %(number)s disks for bus ' %(bus)s ' are supported " ) %
{ " number " : maxnode , " bus " : self . bus } )
2018-09-02 11:57:41 -04:00
2020-01-28 20:03:38 -05: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 11:57:41 -04: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 self . is_disk ( ) and guest . supports_virtiodisk ( ) :
return " virtio "
2019-03-08 11:30:24 -05: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 11:57:41 -04:00
return " scsi "
if guest . os . is_arm ( ) :
return " sd "
if guest . os . is_q35 ( ) :
return " sata "
return " ide "
def set_defaults ( self , guest ) :
2018-09-01 22:16:26 -04:00
if not self . _device :
self . _device = self . _get_device ( )
2020-01-29 06:18:15 -05:00
if not self . _xmltype :
self . _xmltype = self . _get_default_type ( )
2018-09-01 22:16:26 -04: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 11:57:41 -04: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 )