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
# Jeremy Katz <katzj@redhat.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
2013-10-28 00:59:47 +04:00
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
2013-03-18 01:06:52 +04:00
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA.
import os
import stat
import pwd
import subprocess
import logging
import re
2014-09-12 23:59:22 +04:00
from . import diskbackend
from . import util
from . device import VirtualDevice
2016-06-12 00:43:18 +03:00
from . xmlbuilder import XMLBuilder , XMLChildProperty , XMLProperty
2013-03-18 01:06:52 +04:00
2013-04-13 22:34:52 +04:00
2013-03-18 01:06:52 +04:00
def _qemu_sanitize_drvtype ( phystype , fmt , manual_format = False ) :
"""
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
if phystype == VirtualDisk . TYPE_BLOCK :
if not fmt :
2015-09-24 00:26:48 +03:00
return VirtualDisk . DRIVER_TYPE_RAW
2013-03-18 01:06:52 +04:00
if fmt and not manual_format :
2015-09-24 00:26:48 +03:00
return VirtualDisk . DRIVER_TYPE_RAW
2013-03-18 01:06:52 +04:00
if fmt in raw_list :
2015-09-24 00:26:48 +03:00
return VirtualDisk . DRIVER_TYPE_RAW
2013-03-18 01:06:52 +04:00
return fmt
2013-04-13 22:34:52 +04:00
2013-03-18 01:06:52 +04:00
def _is_dir_searchable ( uid , username , path ) :
"""
Check if passed directory is searchable by uid
"""
2015-09-09 20:12:26 +03:00
if " VIRTINST_TEST_SUITE " in os . environ :
return True
2013-03-18 01:06:52 +04:00
try :
statinfo = os . stat ( path )
except OSError :
return False
if uid == statinfo . st_uid :
flag = stat . S_IXUSR
elif uid == statinfo . st_gid :
flag = stat . S_IXGRP
else :
flag = stat . S_IXOTH
if bool ( statinfo . st_mode & flag ) :
return True
# Check POSIX ACL (since that is what we use to 'fix' access)
cmd = [ " getfacl " , path ]
try :
proc = subprocess . Popen ( cmd ,
stdout = subprocess . PIPE ,
stderr = subprocess . PIPE )
out , err = proc . communicate ( )
except OSError :
logging . debug ( " Didn ' t find the getfacl command. " )
return False
if proc . returncode != 0 :
logging . debug ( " Cmd ' %s ' failed: %s " , cmd , err )
return False
return bool ( re . search ( " user: %s :..x " % username , out ) )
2013-04-13 22:34:52 +04:00
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
"""
_XML_ROOT_NAME = " seclabel "
model = XMLProperty ( " ./@model " )
relabel = XMLProperty ( " ./@relabel " , is_yesno = True )
label = XMLProperty ( " ./label " )
2013-03-18 01:06:52 +04:00
class VirtualDisk ( VirtualDevice ) :
2013-07-16 17:14:37 +04:00
virtual_device_type = VirtualDevice . VIRTUAL_DEV_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 "
2013-03-18 01:06:52 +04:00
cache_types = [ 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 "
discard_types = [ DISCARD_MODE_IGNORE , DISCARD_MODE_UNMAP ]
2013-03-18 01:06:52 +04:00
DEVICE_DISK = " disk "
DEVICE_LUN = " lun "
DEVICE_CDROM = " cdrom "
DEVICE_FLOPPY = " floppy "
devices = [ DEVICE_DISK , DEVICE_LUN , DEVICE_CDROM , DEVICE_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 "
types = [ TYPE_FILE , TYPE_BLOCK , TYPE_DIR , TYPE_NETWORK ]
2013-03-18 01:06:52 +04:00
IO_MODE_NATIVE = " native "
IO_MODE_THREADS = " threads "
io_modes = [ IO_MODE_NATIVE , IO_MODE_THREADS ]
2013-08-17 18:35:44 +04:00
error_policies = [ " ignore " , " stop " , " enospace " , " report " ]
2013-03-18 01:06:52 +04:00
@staticmethod
def disk_type_to_xen_driver_name ( disk_type ) :
"""
Convert a value of VirtualDisk . type to it ' s associated Xen
< driver name = / > property
"""
if disk_type == VirtualDisk . TYPE_BLOCK :
return " phy "
elif disk_type == VirtualDisk . TYPE_FILE :
return " file "
return " file "
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
"""
2014-02-04 01:43:25 +04:00
if path is None :
return False
2013-03-18 01:06:52 +04:00
try :
2014-12-05 03:52:32 +03:00
( vol , pool ) = diskbackend . check_if_path_managed ( conn , path )
2014-02-09 01:36:45 +04:00
ignore = pool
2013-03-18 01:06:52 +04:00
2014-12-05 03:52:32 +03:00
if vol :
2013-03-18 01:06:52 +04:00
return True
2013-07-06 04:36:28 +04:00
if not conn . is_remote ( ) :
2013-03-18 01:06:52 +04:00
return os . path . exists ( path )
2017-07-24 11:26:48 +03:00
except Exception :
2013-03-18 01:06:52 +04:00
pass
return False
@staticmethod
def check_path_search_for_user ( conn , path , username ) :
"""
Check if the passed user has search permissions for all the
directories in the disk path .
@return : List of the directories the user cannot search , or empty list
@rtype : C { list }
"""
if path is None :
return [ ]
2013-07-06 04:36:28 +04:00
if conn . is_remote ( ) :
2013-03-18 01:06:52 +04:00
return [ ]
2013-11-10 20:09:22 +04:00
if username == " root " :
return [ ]
2014-12-09 17:20:18 +03:00
if diskbackend . path_is_url ( path ) :
return [ ]
2013-03-18 01:06:52 +04:00
try :
2015-09-09 20:12:26 +03:00
# Get UID for string name
uid = pwd . getpwnam ( username ) [ 2 ]
2017-05-05 19:47:21 +03:00
except Exception as e :
2013-03-18 01:06:52 +04:00
logging . debug ( " Error looking up username: %s " , str ( e ) )
return [ ]
fixlist = [ ]
if os . path . isdir ( path ) :
dirname = path
base = " - "
else :
dirname , base = os . path . split ( path )
while base :
if not _is_dir_searchable ( uid , username , dirname ) :
fixlist . append ( dirname )
dirname , base = os . path . split ( dirname )
return fixlist
2014-02-11 03:08:59 +04:00
@staticmethod
def check_path_search ( conn , path ) :
# Only works for qemu and DAC
if conn . is_remote ( ) or not conn . is_qemu_system ( ) :
return None , [ ]
2015-04-06 22:42:40 +03:00
from virtcli import CLIConfig
user = CLIConfig . default_qemu_user
2014-02-11 03:08:59 +04:00
try :
2015-04-03 19:40:16 +03:00
for secmodel in conn . caps . host . secmodels :
if secmodel . model != " dac " :
2014-02-11 03:08:59 +04:00
continue
2015-04-03 19:40:16 +03:00
label = None
for baselabel in secmodel . baselabels :
if baselabel . type in [ " qemu " , " kvm " ] :
label = baselabel . content
break
2014-02-11 03:08:59 +04:00
if not label :
continue
pwuid = pwd . getpwuid (
int ( label . split ( " : " ) [ 0 ] . replace ( " + " , " " ) ) )
if pwuid :
user = pwuid [ 0 ]
2017-07-24 11:26:48 +03:00
except Exception :
2014-02-11 03:08:59 +04:00
logging . debug ( " Exception grabbing qemu DAC user " , exc_info = True )
return None , [ ]
return user , VirtualDisk . check_path_search_for_user ( conn , path , user )
2013-03-18 01:06:52 +04:00
@staticmethod
def fix_path_search_for_user ( conn , path , username ) :
"""
Try to fix any permission problems found by check_path_search_for_user
2013-04-13 22:34:52 +04:00
@return : Return a dictionary of entries { broken path : error msg }
2013-03-18 01:06:52 +04:00
@rtype : C { dict }
"""
def fix_perms ( dirname , useacl = True ) :
if useacl :
cmd = [ " setfacl " , " --modify " , " user: %s :x " % username , dirname ]
proc = subprocess . Popen ( cmd ,
stdout = subprocess . PIPE ,
stderr = subprocess . PIPE )
out , err = proc . communicate ( )
logging . debug ( " Ran command ' %s ' " , cmd )
if out or err :
logging . debug ( " out= %s \n err= %s " , out , err )
if proc . returncode != 0 :
raise ValueError ( err )
else :
logging . debug ( " Setting +x on %s " , dirname )
mode = os . stat ( dirname ) . st_mode
newmode = mode | stat . S_IXOTH
os . chmod ( dirname , newmode )
if os . stat ( dirname ) . st_mode != newmode :
# Trying to change perms on vfat at least doesn't work
# but also doesn't seem to error. Try and detect that
raise ValueError ( _ ( " Permissions on ' %s ' did not stick " ) %
dirname )
fixlist = VirtualDisk . check_path_search_for_user ( conn , path , username )
if not fixlist :
return [ ]
fixlist . reverse ( )
errdict = { }
useacl = True
for dirname in fixlist :
try :
try :
fix_perms ( dirname , useacl )
2017-07-24 11:26:48 +03:00
except Exception :
2013-03-18 01:06:52 +04:00
# If acl fails, fall back to chmod and retry
if not useacl :
raise
useacl = False
logging . debug ( " setfacl failed, trying old fashioned way " )
fix_perms ( dirname , useacl )
2017-05-05 19:47:21 +03:00
except Exception as e :
2013-03-18 01:06:52 +04:00
errdict [ dirname ] = str ( e )
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 .
@param conn : virConnect to check VMs
@param path : Path to check for
2014-01-15 01:32:05 +04:00
@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.
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 = [ ]
2013-07-07 22:54:48 +04:00
vms = conn . fetch_all_guests ( )
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
2014-01-15 01:32:05 +04:00
for disk in vm . get_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
"""
Helper for building a StorageVolume instance to pass to VirtualDisk
for eventual storage creation .
: param volname : name of the volume to be created
: param size : size in bytes
"""
from . storage import StorageVolume
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 ) :
amt = ( num % ( 26 * * ( factor + 1 ) ) ) / ( 26 * * factor )
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 = [
2017-09-06 10:13:44 +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 " ,
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 ) :
VirtualDevice . __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 ) :
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 :
http : / / lists . gnu . org / archive / html / qemu - devel / 2008 - 04 / msg00675 . html
"""
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 " )
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 ) :
2015-09-06 17:36:17 +03:00
from . uri import URI
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
if poolxml . hosts :
self . source_host_name = poolxml . hosts [ 0 ] . name
self . source_host_port = poolxml . hosts [ 0 ] . port
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 :
return VirtualDisk . 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 :
return VirtualDisk . TYPE_NETWORK
2014-12-10 00:57:32 +03:00
return self . TYPE_FILE
2014-12-07 00:22:00 +03:00
type = XMLProperty ( " ./@type " , default_cb = _get_default_type )
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
if disk_type == VirtualDisk . TYPE_BLOCK :
return " _source_dev "
elif disk_type == VirtualDisk . TYPE_DIR :
return " _source_dir "
2014-12-07 00:22:00 +03:00
elif disk_type == VirtualDisk . 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
2013-09-19 21:27:30 +04:00
device = XMLProperty ( " ./@device " ,
2013-07-15 01:12:47 +04:00
default_cb = lambda s : s . DEVICE_DISK )
2017-09-06 10:13:44 +03:00
snapshot_policy = XMLProperty ( " ./@snapshot " )
2013-09-19 21:27:30 +04:00
driver_name = XMLProperty ( " ./driver/@name " ,
2013-07-15 02:00:43 +04:00
default_cb = _get_default_driver_name )
2013-09-19 21:27:30 +04:00
driver_type = XMLProperty ( " ./driver/@type " ,
2013-07-15 02:00:43 +04:00
default_cb = _get_default_driver_type )
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 " )
2016-06-15 00:29:54 +03:00
def add_seclabel ( self ) :
obj = _DiskSeclabel ( self . conn )
self . add_child ( obj )
return obj
2015-09-05 21:40:45 +03:00
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
2014-12-09 01:28:29 +03:00
if self . type == VirtualDisk . TYPE_NETWORK :
# 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
2014-12-07 02:33:11 +03:00
if typ == VirtualDisk . 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
2014-12-06 20:41:44 +03:00
if ( self . type == VirtualDisk . TYPE_DIR and
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
2013-07-06 04:14:57 +04:00
def setup ( self , meter = None ) :
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
2013-10-06 21:17:35 +04:00
def set_defaults ( self , guest ) :
2013-10-03 00:09:05 +04:00
if self . is_cdrom ( ) :
self . read_only = True
2015-07-13 14:35:24 +03:00
if self . is_cdrom ( ) and guest . os . is_s390x ( ) :
self . bus = " scsi "
2013-08-17 22:21:30 +04:00
if not self . conn . is_qemu ( ) :
return
if not self . is_disk ( ) :
2013-07-14 23:47:02 +04:00
return
2013-10-03 00:09:05 +04:00
if not self . type == self . TYPE_BLOCK :
return
2013-07-14 23:47:02 +04:00
2013-10-03 00:09:05 +04:00
# Enable cache=none and io=native for block devices. Would
# be nice if qemu did this for us but that time has long passed.
2013-08-17 22:21:30 +04:00
if not self . driver_cache :
2013-07-14 23:47:02 +04:00
self . driver_cache = self . CACHE_MODE_NONE
2013-10-03 00:09:05 +04:00
if not self . driver_io :
2013-07-14 23:47:02 +04:00
self . driver_io = self . IO_MODE_NATIVE
2013-08-17 22:21:30 +04:00
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 .
2013-07-08 00:34:46 +04:00
@return : list of colliding VM names
@rtype : C { list }
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 .
@returns : str prefix , or None if no reasonable guess can be made
"""
# The upper limits here aren't necessarilly 1024, but let the HV
# 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
@param skip_targets : list of targets to exclude
@type skip_targets : C { list }
2014-02-12 18:46:35 +04:00
@param pref_ctrl : preferred controller to connect the disk to
@type pref_ctrl : C { int }
2013-03-18 01:06:52 +04:00
@raise ValueError : can ' t determine target type, no targets available
@returns generated target
@rtype C { str }
"""
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 ) ) )
2013-07-24 16:46:55 +04:00
VirtualDisk . register_type ( )