virt-manager/virtinst/VirtualDisk.py
2013-04-11 12:15:37 -04:00

1877 lines
66 KiB
Python

#
# Classes for building disk device xml
#
# Copyright 2006-2008, 2012 Red Hat, Inc.
# 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
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# 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 statvfs
import subprocess
import logging
import re
import urlgrabber.progress as progress
import libvirt
import virtinst
import util
import Storage
from VirtualDevice import VirtualDevice
from XMLBuilderDomain import _xml_property
def _vdisk_create(path, size, kind, sparse=True):
force_fixed = "raw"
path = os.path.expanduser(path)
if kind in force_fixed or not sparse:
_type = kind + ":fixed"
else:
_type = kind + ":sparse"
try:
rc = subprocess.call([ '/usr/sbin/vdiskadm', 'create', '-t', _type,
'-s', str(size), path ])
return rc == 0
except OSError:
return False
def _vdisk_clone(path, clone):
logging.debug("Using vdisk clone.")
path = os.path.expanduser(path)
clone = os.path.expanduser(clone)
try:
rc = subprocess.call([ '/usr/sbin/vdiskadm', 'clone', path, clone ])
return rc == 0
except OSError:
return False
def _qemu_sanitize_drvtype(phystype, fmt, manual_format=False):
"""
Sanitize libvirt storage volume format to a valid qemu driver type
"""
raw_list = [ "iso" ]
if phystype == VirtualDisk.TYPE_BLOCK:
if not fmt:
return VirtualDisk.DRIVER_QEMU_RAW
if fmt and not manual_format:
return VirtualDisk.DRIVER_QEMU_RAW
if fmt in raw_list:
return VirtualDisk.DRIVER_QEMU_RAW
return fmt
def _name_uid(user):
"""
Return UID for string username
"""
pwdinfo = pwd.getpwnam(user)
return pwdinfo[2]
def _is_dir_searchable(uid, username, path):
"""
Check if passed directory is searchable by uid
"""
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))
def _check_if_pool_source(conn, path):
"""
If passed path is a host disk device like /dev/sda, want to let the user
use it
"""
if not util.is_storage_capable(conn):
return None
def check_pool(poolname, path):
pool = conn.storagePoolLookupByName(poolname)
xml = pool.XMLDesc(0)
for element in ["dir", "device", "adapter"]:
xml_path = util.get_xml_path(xml,
"/pool/source/%s/@path" % element)
if xml_path == path:
return pool
running_list = conn.listStoragePools()
inactive_list = conn.listDefinedStoragePools()
for plist in [running_list, inactive_list]:
for name in plist:
p = check_pool(name, path)
if p:
return p
return None
def _check_if_path_managed(conn, path):
"""
Determine if we can use libvirt storage APIs to create or lookup
the passed path. If we can't, throw an error
"""
vol = None
pool = None
verr = None
path_is_pool = False
def lookup_vol_by_path():
try:
vol = conn.storageVolLookupByPath(path)
vol.info()
return vol, None
except libvirt.libvirtError, e:
if (hasattr(libvirt, "VIR_ERR_NO_STORAGE_VOL")
and e.get_error_code() != libvirt.VIR_ERR_NO_STORAGE_VOL):
raise
return None, e
def lookup_vol_name(name):
try:
name = os.path.basename(path)
if pool and name in pool.listVolumes():
return pool.lookupByName(name)
except:
pass
return None
vol = lookup_vol_by_path()[0]
if not vol:
pool = util.lookup_pool_by_path(conn, os.path.dirname(path))
# Is pool running?
if pool and pool.info()[0] != libvirt.VIR_STORAGE_POOL_RUNNING:
pool = None
# Attempt to lookup path as a storage volume
if pool and not vol:
try:
# Pool may need to be refreshed, but if it errors,
# invalidate it
pool.refresh(0)
vol, verr = lookup_vol_by_path()
if verr:
vol = lookup_vol_name(os.path.basename(path))
except Exception, e:
vol = None
pool = None
verr = str(e)
if not vol:
# See if path is a pool source, and allow it through
trypool = _check_if_pool_source(conn, path)
if trypool:
path_is_pool = True
pool = trypool
if not vol and not pool:
if not util.is_uri_remote(conn.getURI(), conn=conn):
# Building local disk
return None, None, False
if not verr:
# Since there is no error, no pool was ever found
err = (_("Cannot use storage '%(path)s': '%(rootdir)s' is "
"not managed on the remote host.") %
{ 'path' : path,
'rootdir' : os.path.dirname(path)})
else:
err = (_("Cannot use storage %(path)s: %(err)s") %
{ 'path' : path, 'err' : verr })
raise ValueError(err)
return vol, pool, path_is_pool
def _build_vol_install(path, pool, size, sparse):
# Path wasn't a volume. See if base of path is a managed
# pool, and if so, setup a StorageVolume object
if size == None:
raise ValueError(_("Size must be specified for non "
"existent volume path '%s'" % path))
logging.debug("Path '%s' is target for pool '%s'. "
"Creating volume '%s'.",
os.path.dirname(path), pool.name(),
os.path.basename(path))
volclass = Storage.StorageVolume.get_volume_for_pool(pool_object=pool)
cap = (size * 1024 * 1024 * 1024)
if sparse:
alloc = 0
else:
alloc = cap
volinst = volclass(name=os.path.basename(path),
capacity=cap, allocation=alloc, pool=pool)
return volinst
class VirtualDisk(VirtualDevice):
"""
Builds a libvirt domain disk xml description
The VirtualDisk class is used for building libvirt domain xml descriptions
for disk devices. If creating a disk object from an existing local block
device or file, a path is all that should be required. If you want to
create a local file, a size also needs to be specified.
The remote case is a bit more complex. The options are:
1. A libvirt virStorageVol instance (passed as 'volObject') for an
existing storage volume.
2. A virtinst L{StorageVolume} instance for creating a volume (passed
as 'volInstall').
3. An active connection ('conn') and a path to a storage volume on
that connection.
4. An active connection and a tuple of the form ("poolname",
"volumename")
5. An active connection and a path. The base of the path must
point to the target path for an active pool.
For cases 3 and 4, the lookup will be performed, and 'vol_object'
will be set to the returned virStorageVol. For the last case, 'volInstall'
will be populated for a StorageVolume instance. All the above cases also
work on a local connection as well, the only difference being that
option 3 won't neccessarily error out if the volume isn't found.
__init__ and setting all properties performs lots of validation,
and will throw ValueError's if problems are found.
"""
_virtual_device_type = VirtualDevice.VIRTUAL_DEV_DISK
DRIVER_FILE = "file"
DRIVER_PHY = "phy"
DRIVER_TAP = "tap"
DRIVER_QEMU = "qemu"
driver_names = [DRIVER_FILE, DRIVER_PHY, DRIVER_TAP, DRIVER_QEMU]
DRIVER_QEMU_RAW = "raw"
# No list here, since there are many other valid values
DRIVER_TAP_RAW = "aio"
DRIVER_TAP_QCOW = "qcow"
DRIVER_TAP_VMDK = "vmdk"
DRIVER_TAP_VDISK = "vdisk"
driver_types = [DRIVER_TAP_RAW, DRIVER_TAP_QCOW,
DRIVER_TAP_VMDK, DRIVER_TAP_VDISK]
CACHE_MODE_NONE = "none"
CACHE_MODE_WRITETHROUGH = "writethrough"
CACHE_MODE_WRITEBACK = "writeback"
cache_types = [CACHE_MODE_NONE, CACHE_MODE_WRITETHROUGH,
CACHE_MODE_WRITEBACK]
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"
types = [TYPE_FILE, TYPE_BLOCK, TYPE_DIR]
_target_props = ["file", "dev", "dir"]
IO_MODE_NATIVE = "native"
IO_MODE_THREADS = "threads"
io_modes = [IO_MODE_NATIVE, IO_MODE_THREADS]
@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"
@staticmethod
def disk_type_to_target_prop(disk_type):
"""
Convert a value of VirtualDisk.type to it's associated XML
target property name
"""
if disk_type == VirtualDisk.TYPE_FILE:
return "file"
elif disk_type == VirtualDisk.TYPE_BLOCK:
return "dev"
elif disk_type == VirtualDisk.TYPE_DIR:
return "dir"
return "file"
error_policies = ["ignore", "stop", "enospace"]
@staticmethod
def path_exists(conn, path):
"""
Check if path exists. If we can't determine, return False
"""
is_remote = util.is_uri_remote(conn.getURI(), conn=conn)
try:
vol = None
path_is_pool = False
try:
vol, ignore, path_is_pool = _check_if_path_managed(conn, path)
except:
pass
if vol or path_is_pool:
return True
if not is_remote:
return os.path.exists(path)
except:
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 []
if util.is_uri_remote(conn.getURI(), conn=conn):
return []
try:
uid = _name_uid(username)
except Exception, e:
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
@staticmethod
def fix_path_search_for_user(conn, path, username):
"""
Try to fix any permission problems found by check_path_search_for_user
@return: Return a dictionary of entries { broken path : error msg }
@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\nerr=%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)
except:
# 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)
except Exception, e:
errdict[dirname] = str(e)
return errdict
@staticmethod
def path_in_use_by(conn, path, check_conflict=False):
"""
Return a list of VM names that are using the passed path.
@param conn: virConnect to check VMs
@param path: Path to check for
@param check_conflict: Only return names that are truly conflicting:
this will omit guests that are using the disk
with the 'shareable' flag, and possible other
heuristics
"""
if not path:
return
active, inactive = util.fetch_all_guests(conn)
vms = active + inactive
def count_cb(ctx):
c = 0
template = "count(/domain/devices/disk["
if check_conflict:
template += "not(shareable) and "
template += "source/@%s='%s'])"
for dtype in VirtualDisk._target_props:
xpath = template % (dtype, util.xml_escape(path))
c += ctx.xpathEval(xpath)
return c
names = []
for vm in vms:
xml = vm.XMLDesc(0)
tmpcount = util.get_xml_path(xml, func=count_cb)
if tmpcount:
names.append(vm.name())
return names
@staticmethod
def stat_local_path(path):
"""
Return tuple (storage type, storage size) for the passed path on
the local machine. This is a best effort attempt.
@return: tuple of
(True if regular file, False otherwise, default is True,
max size of storage, default is 0)
"""
try:
return util.stat_disk(path)
except:
return (True, 0)
@staticmethod
def lookup_vol_object(conn, name_tuple):
"""
Return a volume instance from parameters that are passed
to disks volName init parameter
"""
if (type(name_tuple) is not tuple or
len(name_tuple) != 2 or
(type(name_tuple[0]) is not type(name_tuple[1]) is not str)):
raise ValueError(_("volName must be a tuple of the form "
"('poolname', 'volname')"))
if not conn:
raise ValueError(_("'volName' requires a passed connection."))
if not util.is_storage_capable(conn):
raise ValueError(_("Connection does not support storage lookup."))
try:
pool = conn.storagePoolLookupByName(name_tuple[0])
return pool.storageVolLookupByName(name_tuple[1])
except Exception, e:
raise ValueError(_("Couldn't lookup volume object: %s" % str(e)))
def __init__(self, path=None, size=None, transient=False, type=None,
device=None, driverName=None, driverType=None,
readOnly=False, sparse=True, conn=None, volObject=None,
volInstall=None, volName=None, bus=None, shareable=False,
driverCache=None, selinuxLabel=None, format=None,
validate=True, parsexml=None, parsexmlnode=None, caps=None,
driverIO=None, sizebytes=None):
"""
@param path: filesystem path to the disk image.
@type path: C{str}
@param size: size of local file to create in gigabytes
@type size: C{int} or C{long} or C{float}
@param transient: whether to keep disk around after guest install
@type transient: C{bool}
@param type: disk media type (file, block, ...)
@type type: C{str}
@param device: Emulated device type (disk, cdrom, floppy, ...)
@type device: member of devices
@param driverName: name of driver
@type driverName: member of driver_names
@param driverType: type of driver
@type driverType: member of driver_types
@param readOnly: Whether emulated disk is read only
@type readOnly: C{bool}
@param sparse: Create file as a sparse file
@type sparse: C{bool}
@param conn: Connection disk is being installed on
@type conn: libvirt.virConnect
@param volObject: libvirt storage volume object to use
@type volObject: libvirt.virStorageVol
@param volInstall: StorageVolume instance to build for new storage
@type volInstall: L{StorageVolume}
@param volName: Existing StorageVolume lookup information,
(parent pool name, volume name)
@type volName: C{tuple} of (C{str}, C{str})
@param bus: Emulated bus type (ide, scsi, virtio, ...)
@type bus: C{str}
@param shareable: If disk can be shared among VMs
@type shareable: C{bool}
@param driverCache: Disk cache mode (none, writethrough, writeback)
@type driverCache: member of cache_types
@param selinuxLabel: Used for labelling new or relabel existing storage
@type selinuxLabel: C{str}
@param format: Storage volume format to use when creating storage
@type format: C{str}
@param validate: Whether to validate passed parameters against the
local system. Omitting this may cause issues, be
warned!
@type validate: C{bool}
@param sizebytes: Optionally specify storage size in bytes. Takes
precedence over size if specified.
@type sizebytes: C{int}
"""
VirtualDevice.__init__(self, conn=conn,
parsexml=parsexml, parsexmlnode=parsexmlnode,
caps=caps)
self._path = None
self._size = None
self._type = None
self._device = None
self._sparse = None
self._readOnly = None
self._vol_object = None
self._pool_object = None
self._vol_install = None
self._bus = None
self._shareable = None
self._driver_cache = None
self._selinux_label = None
self._clone_path = None
self._format = None
self._driverName = driverName
self._driverType = driverType
self._driver_io = None
self._error_policy = None
self._serial = None
self._target = None
self._iotune_read_bytes_sec = None
self._iotune_read_iops_sec = None
self._iotune_total_bytes_sec = None
self._iotune_total_iops_sec = None
self._iotune_write_bytes_sec = None
self._iotune_write_iops_sec = None
self._validate = validate
# XXX: No property methods for these
self.transient = transient
if volName and not volObject:
volObject = self.lookup_vol_object(conn, volName)
if sizebytes is not None:
size = (float(sizebytes) / float(1024 ** 3))
if self._is_parse():
self._validate = False
return
self.set_read_only(readOnly, validate=False)
self.set_sparse(sparse, validate=False)
self.set_type(type, validate=False)
self.set_device(device or self.DEVICE_DISK, validate=False)
self._set_path(path, validate=False)
self._set_size(size, validate=False)
self._set_vol_object(volObject, validate=False)
self._set_vol_install(volInstall, validate=False)
self._set_bus(bus, validate=False)
self._set_shareable(shareable, validate=False)
self._set_driver_cache(driverCache, validate=False)
self._set_selinux_label(selinuxLabel, validate=False)
self._set_format(format, validate=False)
self._set_driver_io(driverIO, validate=False)
self.__change_storage(self.path,
self.vol_object,
self.vol_install)
self.__validate_params()
#
# Parameters for specifying the backing storage
#
def _get_path(self):
retpath = self._path
if self.vol_object:
retpath = self.vol_object.path()
elif self.vol_install:
retpath = (util.get_xml_path(self.vol_install.pool.XMLDesc(0),
"/pool/target/path") + "/" +
self.vol_install.name)
return retpath
def _set_path(self, val, validate=True):
if val is not None:
self._check_str(val, "path")
val = os.path.abspath(val)
if validate:
self.__change_storage(path=val)
self.__validate_wrapper("_path", val, validate, self.path)
def _xml_get_xpath(self):
xpath = None
for prop in self._target_props:
xpath = "./source/@" + prop
if self._xml_ctx.xpathEval(xpath):
return xpath
return "./source/@file"
def _xml_set_xpath(self):
return "./source/@" + self.disk_type_to_target_prop(self.type)
path = _xml_property(_get_path, _set_path,
xml_get_xpath=_xml_get_xpath,
xml_set_xpath=_xml_set_xpath,)
def _get_vol_object(self):
return self._vol_object
def _set_vol_object(self, val, validate=True):
if val is not None and not isinstance(val, libvirt.virStorageVol):
raise ValueError(_("vol_object must be a virStorageVol instance"))
if validate:
self.__change_storage(vol_object=val)
self.__validate_wrapper("_vol_object", val, validate, self.vol_object)
vol_object = property(_get_vol_object, _set_vol_object)
def _get_vol_install(self):
return self._vol_install
def _set_vol_install(self, val, validate=True):
if val is not None and not isinstance(val, Storage.StorageVolume):
raise ValueError(_("vol_install must be a StorageVolume "
" instance."))
if validate:
self.__change_storage(vol_install=val)
self.__validate_wrapper("_vol_install", val, validate, self.vol_install)
vol_install = property(_get_vol_install, _set_vol_install)
#
# Other properties
#
def _get_clone_path(self):
return self._clone_path
def _set_clone_path(self, val, validate=True):
if val is not None:
self._check_str(val, "path")
val = os.path.abspath(val)
# Pass the path to a VirtualDisk, which should provide validation
# for us
try:
# If this disk isn't managed, don't pass 'conn' to this
# validation disk, to ensure we have permissions for manual
# cloning
conn = self.__managed_storage() and self.conn or None
VirtualDisk(conn=conn, path=val)
except Exception, e:
raise ValueError(_("Error validating clone path: %s") % e)
self.__validate_wrapper("_clone_path", val, validate, self.clone_path)
clone_path = property(_get_clone_path, _set_clone_path)
def _get_size(self):
retsize = self.__existing_storage_size()
if retsize is None:
if self.vol_install:
retsize = self.vol_install.capacity / 1024.0 / 1024.0 / 1024.0
else:
retsize = self._size
return retsize
def _set_size(self, val, validate=True):
if val is not None:
if type(val) not in [int, float, long] or val < 0:
raise ValueError(_("'size' must be a number greater than 0."))
self.__validate_wrapper("_size", val, validate, self.size)
size = property(_get_size, _set_size)
def get_type(self):
if self._type:
return self._type
return self.__existing_storage_dev_type()
def set_type(self, val, validate=True):
if val is not None:
self._check_str(val, "type")
if val not in self.types:
raise ValueError(_("Unknown storage type '%s'" % val))
self.__validate_wrapper("_type", val, validate, self.type)
type = _xml_property(get_type, set_type,
xpath="./@type")
def get_device(self):
return self._device
def set_device(self, val, validate=True):
self._check_str(val, "device")
if val not in self.devices:
raise ValueError(_("Unknown device type '%s'" % val))
if val == self._device:
return
if self._is_parse():
self.bus = None
self.target = None
self.__validate_wrapper("_device", val, validate, self.device)
device = _xml_property(get_device, set_device,
xpath="./@device")
def get_driver_name(self):
retname = self._driverName
if not retname:
retname, ignore = self.__get_default_driver()
return retname
def set_driver_name(self, val, validate=True):
ignore = validate
self._driverName = val
driver_name = _xml_property(get_driver_name, set_driver_name,
xpath="./driver/@name")
def get_driver_type(self):
rettype = self._driverType
if not rettype:
ignore, rettype = self.__get_default_driver()
return rettype
def set_driver_type(self, val, validate=True):
ignore = validate
self._driverType = val
driver_type = _xml_property(get_driver_type, set_driver_type,
xpath="./driver/@type")
def get_sparse(self):
return self._sparse
def set_sparse(self, val, validate=True):
self._check_bool(val, "sparse")
self.__validate_wrapper("_sparse", val, validate, self.sparse)
sparse = property(get_sparse, set_sparse)
def get_read_only(self):
return self._readOnly
def set_read_only(self, val, validate=True):
self._check_bool(val, "read_only")
self.__validate_wrapper("_readOnly", val, validate, self.read_only)
read_only = _xml_property(get_read_only, set_read_only,
xpath="./readonly", is_bool=True)
def _get_bus(self):
return self._bus
def _set_bus(self, val, validate=True):
if val is not None:
self._check_str(val, "bus")
self.__validate_wrapper("_bus", val, validate, self.bus)
bus = _xml_property(_get_bus, _set_bus,
xpath="./target/@bus")
def _get_target(self):
return self._target
def _set_target(self, val, validate=True):
ignore = validate
if val is not None:
self._check_str(val, "target")
self._target = val
target = _xml_property(_get_target, _set_target,
xpath="./target/@dev")
def _get_shareable(self):
return self._shareable
def _set_shareable(self, val, validate=True):
self._check_bool(val, "shareable")
self.__validate_wrapper("_shareable", val, validate, self.shareable)
shareable = _xml_property(_get_shareable, _set_shareable,
xpath="./shareable", is_bool=True)
def _get_driver_cache(self):
return self._driver_cache
def _set_driver_cache(self, val, validate=True):
if val is not None:
self._check_str(val, "cache")
if val not in self.cache_types:
raise ValueError(_("Unknown cache mode '%s'" % val))
self.__validate_wrapper("_driver_cache", val, validate,
self.driver_cache)
driver_cache = _xml_property(_get_driver_cache, _set_driver_cache,
xpath="./driver/@cache")
def _get_driver_io(self):
return self._driver_io
def _set_driver_io(self, val, validate=True):
if val is not None:
self._check_str(val, "driver_io")
if val not in self.io_modes:
raise ValueError(_("Unknown io mode '%s'" % val))
self.__validate_wrapper("_driver_io", val, validate,
self.driver_io)
driver_io = _xml_property(_get_driver_io, _set_driver_io,
xpath="./driver/@io")
def _get_error_policy(self):
return self._error_policy
def _set_error_policy(self, val, validate=True):
if val is not None:
self._check_str(val, "error_policy")
if val not in self.error_policies:
raise ValueError(_("Unknown error policy '%s'" % val))
self.__validate_wrapper("_error_policy", val, validate,
self.error_policy)
error_policy = _xml_property(_get_error_policy, _set_error_policy,
xpath="./driver/@error_policy")
def _get_serial(self):
return self._serial
def _set_serial(self, val, validate=True):
if val is not None:
self._check_str(val, "serial")
self.__validate_wrapper("_serial", val, validate,
self.serial)
serial = _xml_property(_get_serial, _set_serial,
xpath="./serial")
def _get_iotune_read_bytes_sec(self):
return self._iotune_read_bytes_sec
def _set_iotune_read_bytes_sec(self, val):
if (type(val) is not type(1) or val < 0):
raise ValueError(_("IOTune read bytes per second value must be an "
"integer"))
self._iotune_read_bytes_sec = val
iotune_read_bytes_sec = _xml_property(_get_iotune_read_bytes_sec,
_set_iotune_read_bytes_sec,
xpath="./iotune/read_bytes_sec",
get_converter=lambda s, x: int(x or 0),
set_converter=lambda s, x: int(x))
def _get_iotune_read_iops_sec(self):
return self._iotune_read_iops_sec
def _set_iotune_read_iops_sec(self, val):
if (type(val) is not type(1) or val < 0):
raise ValueError(_("IOTune read iops per second value must be an "
"integer"))
self._iotune_read_iops_sec = val
iotune_read_iops_sec = _xml_property(_get_iotune_read_iops_sec,
_set_iotune_read_iops_sec,
xpath="./iotune/read_iops_sec",
get_converter=lambda s, x: int(x or 0),
set_converter=lambda s, x: int(x))
def _get_iotune_total_bytes_sec(self):
return self._iotune_total_bytes_sec
def _set_iotune_total_bytes_sec(self, val):
if (type(val) is not type(1) or val < 0):
raise ValueError(_("IOTune total bytes per second value must be an "
"integer"))
self._iotune_total_bytes_sec = val
iotune_total_bytes_sec = _xml_property(_get_iotune_total_bytes_sec,
_set_iotune_total_bytes_sec,
xpath="./iotune/total_bytes_sec",
get_converter=lambda s, x: int(x or 0),
set_converter=lambda s, x: int(x))
def _get_iotune_total_iops_sec(self):
return self._iotune_total_iops_sec
def _set_iotune_total_iops_sec(self, val):
if (type(val) is not type(1) or val < 0):
raise ValueError(_("IOTune total iops per second value must be an "
"integer"))
self._iotune_total_iops_sec = val
iotune_total_iops_sec = _xml_property(_get_iotune_total_iops_sec,
_set_iotune_total_iops_sec,
xpath="./iotune/total_iops_sec",
get_converter=lambda s, x: int(x or 0),
set_converter=lambda s, x: int(x))
def _get_iotune_write_bytes_sec(self):
return self._iotune_write_bytes_sec
def _set_iotune_write_bytes_sec(self, val):
if (type(val) is not type(1) or val < 0):
raise ValueError(_("IOTune write bytes per second value must be an "
"integer"))
self._iotune_write_bytes_sec = val
iotune_write_bytes_sec = _xml_property(_get_iotune_write_bytes_sec,
_set_iotune_write_bytes_sec,
xpath="./iotune/write_bytes_sec",
get_converter=lambda s, x: int(x or 0),
set_converter=lambda s, x: int(x))
def _get_iotune_write_iops_sec(self):
return self._iotune_write_iops_sec
def _set_iotune_write_iops_sec(self, val):
if (type(val) is not type(1) or val < 0):
raise ValueError(_("IOTune write iops per second value must be an "
"integer"))
self._iotune_write_iops_sec = val
iotune_write_iops_sec = _xml_property(_get_iotune_write_iops_sec,
_set_iotune_write_iops_sec,
xpath="./iotune/write_iops_sec",
get_converter=lambda s, x: int(x or 0),
set_converter=lambda s, x: int(x))
# If there is no selinux support on the libvirt connection or the
# system, we won't throw errors if this is set, just silently ignore.
def _get_selinux_label(self):
# If selinux_label manually specified, return it
# If we are using existing storage, pull the label from it
# If we are installing via vol_install, pull from the parent pool
# If we are creating local storage, use the expected label
retlabel = self._selinux_label
if not retlabel:
retlabel = ""
if self.creating_storage() and not self.__managed_storage():
retlabel = self._expected_security_label()
else:
retlabel = self._storage_security_label()
return retlabel
def _set_selinux_label(self, val, validate=True):
if val is not None:
self._check_str(val, "selinux_label")
if (self._support_selinux() and
not util.selinux_is_label_valid(val)):
# XXX Not valid if we support changing labels remotely
raise ValueError(_("SELinux label '%s' is not valid.") % val)
self.__validate_wrapper("_selinux_label", val, validate,
self.selinux_label)
selinux_label = property(_get_selinux_label, _set_selinux_label)
def _get_format(self):
return self._format
def _set_format(self, val, validate=True):
if val is not None:
self._check_str(val, "format")
self.__validate_wrapper("_format", val, validate, self.format)
format = property(_get_format, _set_format)
# Validation assistance methods
# Initializes attribute if it hasn't been done, then validates args.
# If validation fails, reset attribute to original value and raise error
def __validate_wrapper(self, varname, newval, validate, origval):
orig = origval
setattr(self, varname, newval)
if validate:
try:
self.__validate_params()
except:
setattr(self, varname, orig)
raise
def can_be_empty(self):
return (self.device == self.DEVICE_FLOPPY or
self.device == self.DEVICE_CDROM)
def __change_storage(self, path=None, vol_object=None, vol_install=None):
"""
Validates and updates params when the backing storage is changed
"""
pool = None
storage_capable = bool(self.conn and
util.is_storage_capable(self.conn))
# Try to lookup self.path storage objects
if vol_object or vol_install:
pass
elif not storage_capable:
pass
elif path:
vol_object, pool, path_is_pool = _check_if_path_managed(self.conn,
path)
if (pool and
not vol_object and
not path_is_pool and
not self._is_parse()):
vol_install = _build_vol_install(path, pool,
self.size,
self.sparse)
if not path_is_pool:
pool = None
# Finally, set the relevant params
self._set_path(path, validate=False)
self._set_vol_object(vol_object, validate=False)
self._set_vol_install(vol_install, validate=False)
self._pool_object = pool
# XXX: Hack, we shouldn't have to conditionalize for parsing
if self._is_parse():
self.type = self.get_type()
self.driver_name = self.get_driver_name()
self.driver_type = self.get_driver_type()
def __set_format(self):
if not self.format:
return
if not self.creating_storage():
return
if self.vol_install:
if not hasattr(self.vol_install, "format"):
raise ValueError(_("Storage type does not support format "
"parameter."))
if self.vol_install.format != self.format:
self.vol_install.format = self.format
elif self.format != "raw":
raise RuntimeError(_("Format cannot be specified for "
"unmanaged storage."))
def __existing_storage_size(self):
"""
Return size of existing storage
"""
if self.creating_storage():
return
if self.vol_object:
newsize = util.get_xml_path(self.vol_object.XMLDesc(0),
"/volume/capacity")
try:
newsize = float(newsize) / 1024.0 / 1024.0 / 1024.0
except:
newsize = 0
elif self._pool_object:
newsize = util.get_xml_path(self.vol_object.XMLDesc(0),
"/pool/capacity")
try:
newsize = float(newsize) / 1024.0 / 1024.0 / 1024.0
except:
newsize = 0
elif self.path is None:
newsize = 0
else:
ignore, newsize = util.stat_disk(self.path)
newsize = newsize / 1024.0 / 1024.0 / 1024.0
return newsize
def __existing_storage_dev_type(self):
"""
Detect disk 'type' () from passed storage parameters
"""
dtype = None
if self.vol_object:
# vol info is [ vol type (file or block), capacity, allocation ]
t = self.vol_object.info()[0]
if t == libvirt.VIR_STORAGE_VOL_FILE:
dtype = self.TYPE_FILE
elif t == libvirt.VIR_STORAGE_VOL_BLOCK:
dtype = self.TYPE_BLOCK
else:
dtype = self.TYPE_FILE
elif self.vol_install:
if self.vol_install.file_type == libvirt.VIR_STORAGE_VOL_FILE:
dtype = self.TYPE_FILE
else:
dtype = self.TYPE_BLOCK
elif self._pool_object:
xml = self._pool_object.XMLDesc(0)
for source, source_type in [("dir", self.TYPE_DIR),
("device", self.TYPE_BLOCK),
("adapter", self.TYPE_BLOCK)]:
if util.get_xml_path(xml, "/pool/source/%s/@dev" % source):
dtype = source_type
break
elif self.path:
if os.path.isdir(self.path):
dtype = self.TYPE_DIR
elif util.stat_disk(self.path)[0]:
dtype = self.TYPE_FILE
else:
dtype = self.TYPE_BLOCK
if not dtype:
dtype = self._type or self.TYPE_BLOCK
return dtype
def __get_default_driver(self):
"""
Set driverName and driverType from passed parameters
Where possible, we want to force driverName = "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
"""
drvname = self._driverName
drvtype = self._driverType
if self.conn:
is_qemu = self.is_qemu()
if is_qemu and not drvname:
drvname = self.DRIVER_QEMU
if self.format:
if drvname == self.DRIVER_QEMU:
drvtype = _qemu_sanitize_drvtype(self.type, self.format,
manual_format=True)
elif self.vol_object:
fmt = util.get_xml_path(self.vol_object.XMLDesc(0),
"/volume/target/format/@type")
if drvname == self.DRIVER_QEMU:
drvtype = _qemu_sanitize_drvtype(self.type, fmt)
elif self.vol_install:
if drvname == self.DRIVER_QEMU:
if hasattr(self.vol_install, "format"):
drvtype = _qemu_sanitize_drvtype(self.type,
self.vol_install.format)
elif self.creating_storage():
if drvname == self.DRIVER_QEMU:
drvtype = self.DRIVER_QEMU_RAW
elif self.path and os.path.exists(self.path):
if util.is_vdisk(self.path):
drvname = self.DRIVER_TAP
drvtype = self.DRIVER_TAP_VDISK
return drvname or None, drvtype or None
def __managed_storage(self):
"""
Return bool representing if managed storage parameters have
been explicitly specified or filled in
"""
return bool(self.vol_object != None or
self.vol_install != None or
self._pool_object != None)
def creating_storage(self):
"""
Return True if the user requested us to create a device
"""
if self.__no_storage():
return False
if self.__managed_storage():
if self.vol_object or self._pool_object:
return False
return True
if (not self.is_remote() and
self.path and
os.path.exists(self.path)):
return False
return True
def __no_storage(self):
"""
Return True if no path or storage was specified
"""
if self.__managed_storage():
return False
if self.path:
return False
return True
def _storage_security_label(self):
"""
Return SELinux label of existing storage, or None
"""
context = ""
if self.__no_storage():
return context
if self.vol_object:
context = util.get_xml_path(self.vol_object.XMLDesc(0),
"/volume/target/permissions/label")
elif self._pool_object:
context = util.get_xml_path(self._pool_object.XMLDesc(0),
"/pool/target/permissions/label")
elif self.vol_install:
# XXX: If user entered a manual label, should we sync this
# to vol_install?
l = util.get_xml_path(self.vol_install.pool.XMLDesc(0),
"/pool/target/permissions/label")
context = l or ""
else:
context = util.selinux_getfilecon(self.path)
return context
def __validate_params(self):
"""
function to validate all the complex interaction between the various
disk parameters.
"""
if not self._validate:
return
# No storage specified for a removable device type (CDROM, floppy)
if self.__no_storage():
if not self.can_be_empty():
raise ValueError(_("Device type '%s' requires a path") %
self.device)
return True
storage_capable = bool(self.conn and
util.is_storage_capable(self.conn))
if self.is_remote():
if not storage_capable:
raise ValueError(_("Connection doesn't support remote "
"storage."))
if not self.__managed_storage():
raise ValueError(_("Must specify libvirt managed storage "
"if on a remote connection"))
# The main distinctions from this point forward:
# - Are we doing storage API operations or local media checks?
# - Do we need to create the storage?
managed_storage = self.__managed_storage()
create_media = self.creating_storage()
self.__set_format()
# If not creating the storage, our job is easy
if not create_media:
# Make sure we have access to the local path
if not managed_storage:
if (os.path.isdir(self.path) and
not util.is_vdisk(self.path) and
not self.device == self.DEVICE_FLOPPY):
raise ValueError(_("The path '%s' must be a file or a "
"device, not a directory") % self.path)
return True
if (self.device == self.DEVICE_FLOPPY or
self.device == self.DEVICE_CDROM):
raise ValueError(_("Cannot create storage for %s device.") %
self.device)
if not managed_storage:
if self.type is self.TYPE_BLOCK:
raise ValueError(_("Local block device path '%s' must "
"exist.") % self.path)
# Path doesn't exist: make sure we have write access to dir
if not os.access(os.path.dirname(self.path), os.R_OK):
raise ValueError("No read access to directory '%s'" %
os.path.dirname(self.path))
if self.size is None:
raise ValueError(_("size is required for non-existent disk "
"'%s'" % self.path))
if not os.access(os.path.dirname(self.path), os.W_OK):
raise ValueError(_("No write access to directory '%s'") %
os.path.dirname(self.path))
# Applicable for managed or local storage
ret = self.is_size_conflict()
if ret[0]:
raise ValueError(ret[1])
elif ret[1]:
logging.warn(ret[1])
# Storage creation routines
def _do_create_storage(self, progresscb):
# If a clone_path is specified, but not vol_install.input_vol,
# that means we are cloning unmanaged -> managed, so skip this
if (self.vol_install and
(not self.clone_path or self.vol_install.input_vol)):
self._set_vol_object(self.vol_install.install(meter=progresscb),
validate=False)
# Then just leave: vol_install should handle any selinux stuff
return
if self.clone_path:
text = (_("Cloning %(srcfile)s") %
{'srcfile' : os.path.basename(self.clone_path)})
else:
text = _("Creating storage file %s") % os.path.basename(self.path)
size_bytes = long(self.size * 1024L * 1024L * 1024L)
progresscb.start(filename=self.path, size=long(size_bytes),
text=text)
if self.clone_path:
# VDisk clone
if (util.is_vdisk(self.clone_path) or
(os.path.exists(self.path) and util.is_vdisk(self.path))):
if (not util.is_vdisk(self.clone_path) or
os.path.exists(self.path)):
raise RuntimeError(_("copying to an existing vdisk is not"
" supported"))
if not _vdisk_clone(self.clone_path, self.path):
raise RuntimeError(_("failed to clone disk"))
progresscb.end(size_bytes)
else:
# Plain file clone
self._clone_local(progresscb, size_bytes)
elif util.is_vdisk(self.path):
# Create vdisk
progresscb.update(1024)
if not _vdisk_create(self.path, size_bytes, "vmdk", self.sparse):
raise RuntimeError(_("Error creating vdisk %s" % self.path))
progresscb.end(self.size)
else:
# Plain file creation
self._create_local_file(progresscb, size_bytes)
def _create_local_file(self, progresscb, size_bytes):
"""
Helper function which attempts to build self.path
"""
fd = None
path = self.path
sparse = self.sparse
try:
try:
fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_DSYNC)
if sparse:
os.ftruncate(fd, size_bytes)
else:
# 1 meg of nulls
mb = 1024 * 1024
buf = '\x00' * mb
left = size_bytes
while left > 0:
if left < mb:
buf = '\x00' * left
left = max(left - mb, 0)
os.write(fd, buf)
progresscb.update(size_bytes - left)
except OSError, e:
raise RuntimeError(_("Error creating diskimage %s: %s") %
(path, str(e)))
finally:
if fd is not None:
os.close(fd)
progresscb.end(size_bytes)
def _clone_local(self, meter, size_bytes):
# if a destination file exists and sparse flg is True,
# this priority takes a existing file.
if (os.path.exists(self.path) == False and self.sparse == True):
clone_block_size = 4096
sparse = True
fd = None
try:
fd = os.open(self.path, os.O_WRONLY | os.O_CREAT)
os.ftruncate(fd, size_bytes)
finally:
if fd:
os.close(fd)
else:
clone_block_size = 1024 * 1024 * 10
sparse = False
logging.debug("Local Cloning %s to %s, sparse=%s, block_size=%s",
self.clone_path, self.path, sparse, clone_block_size)
zeros = '\0' * 4096
src_fd, dst_fd = None, None
try:
try:
src_fd = os.open(self.clone_path, os.O_RDONLY)
dst_fd = os.open(self.path, os.O_WRONLY | os.O_CREAT)
i = 0
while 1:
l = os.read(src_fd, clone_block_size)
s = len(l)
if s == 0:
meter.end(size_bytes)
break
# check sequence of zeros
if sparse and zeros == l:
os.lseek(dst_fd, s, 1)
else:
b = os.write(dst_fd, l)
if s != b:
meter.end(i)
break
i += s
if i < size_bytes:
meter.update(i)
except OSError, e:
raise RuntimeError(_("Error cloning diskimage %s to %s: %s") %
(self.clone_path, self.path, str(e)))
finally:
if src_fd is not None:
os.close(src_fd)
if dst_fd is not None:
os.close(dst_fd)
def setup_dev(self, conn=None, meter=None):
"""
Build storage (if required)
If storage doesn't exist (a non-existent file 'path', or 'vol_install'
was specified), we create it.
@param conn: Optional connection to use if self.conn not specified
@param meter: Progress meter to report file creation on
@type meter: instanceof urlgrabber.BaseMeter
"""
return self.setup(meter)
def setup(self, progresscb=None):
"""
DEPRECATED: Please use setup_dev instead
"""
if not progresscb:
progresscb = progress.BaseMeter()
if self.creating_storage() or self.clone_path:
self._do_create_storage(progresscb)
# Relabel storage if it was requested
storage_label = self._storage_security_label()
if storage_label and storage_label != self.selinux_label:
if not self._support_selinux():
logging.debug("No support for changing selinux context.")
elif not self._security_can_fix():
logging.debug("Can't fix selinux context in this case.")
else:
logging.debug("Changing path=%s selinux label %s -> %s",
self.path, storage_label, self.selinux_label)
util.selinux_setfilecon(self.path, self.selinux_label)
def _get_xml_config(self, disknode=None):
"""
@param disknode: device name in host (xvda, hdb, etc.). self.target
takes precedence.
@type disknode: C{str}
"""
typeattr = self.type
if self.type == VirtualDisk.TYPE_BLOCK:
typeattr = 'dev'
if self.target:
disknode = self.target
if not disknode:
raise ValueError(_("'disknode' or self.target must be set!"))
path = None
if self.vol_object:
path = self.vol_object.path()
elif self.path:
path = self.path
if path:
path = util.xml_escape(path)
ret = " <disk type='%s' device='%s'>\n" % (self.type, self.device)
cache = self.driver_cache
iomode = self.driver_io
if virtinst.enable_rhel6_defaults:
# Enable cache=none for non-CDROM devs
if (self.is_qemu() and
not cache and
self.device != self.DEVICE_CDROM):
cache = self.CACHE_MODE_NONE
# Enable AIO native for block devices
if (self.is_qemu() and
not iomode and
self.device == self.DEVICE_DISK and
self.type == self.TYPE_BLOCK):
iomode = self.IO_MODE_NATIVE
if path:
drvxml = ""
if not self.driver_type is None:
drvxml += " type='%s'" % self.driver_type
if not cache is None:
drvxml += " cache='%s'" % cache
if not self.error_policy is None:
drvxml += " error_policy='%s'" % self.error_policy
if not iomode is None:
drvxml += " io='%s'" % iomode
if drvxml and self.driver_name is None:
if self.is_qemu():
self.driver_name = "qemu"
if not self.driver_name is None:
drvxml = (" name='%s'" % self.driver_name) + drvxml
if drvxml:
ret += " <driver%s/>\n" % drvxml
if path is not None:
ret += " <source %s='%s'/>\n" % (typeattr, path)
bus_xml = ""
if self.bus is not None:
bus_xml = " bus='%s'" % self.bus
ret += " <target dev='%s'%s/>\n" % (disknode, bus_xml)
ro = self.read_only
if self.device == self.DEVICE_CDROM:
ro = True
if self.shareable:
ret += " <shareable/>\n"
if ro:
ret += " <readonly/>\n"
if self.serial:
ret += (" <serial>%s</serial>\n" %
util.xml_escape(self.serial))
if (self.iotune_read_bytes_sec or self.iotune_read_iops_sec or
self.iotune_total_bytes_sec or self.iotune_total_iops_sec or
self.iotune_write_bytes_sec or self.iotune_write_iops_sec):
ret += " <iotune>"
if self.iotune_read_bytes_sec:
ret += " <read_bytes_sec>%s</read_bytes_sec>" % (self.iotune_read_bytes_sec)
if self.iotune_read_iops_sec:
ret += " <read_iops_sec>%s</read_iops_sec>" % (self.iotune_read_iops_sec)
if self.iotune_total_bytes_sec:
ret += " <total_bytes_sec>%s</total_bytes_sec>" % (self.iotune_total_bytes_sec)
if self.iotune_total_iops_sec:
ret += " <total_iops_sec>%s</total_iops_sec>" % (self.iotune_total_iops_sec)
if self.iotune_write_bytes_sec:
ret += " <write_bytes_sec>%s</write_bytes_sec>" % (self.iotune_write_bytes_sec)
if self.iotune_write_iops_sec:
ret += " <write_iops_sec>%s</write_iops_sec>" % (self.iotune_write_iops_sec)
ret += " </iotune>"
ret += " </disk>"
return ret
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")
"""
if self.vol_install:
return self.vol_install.is_size_conflict()
if not self.creating_storage():
return (False, None)
ret = False
msg = None
vfs = os.statvfs(os.path.dirname(self.path))
avail = vfs[statvfs.F_FRSIZE] * vfs[statvfs.F_BAVAIL]
need = long(self.size * 1024L * 1024L * 1024L)
if need > avail:
if self.sparse:
msg = _("The filesystem will not have enough free space"
" to fully allocate the sparse file when the guest"
" is running.")
else:
ret = True
msg = _("There is not enough free space to create the disk.")
if msg:
msg += (_(" %d M requested > %d M available") %
((need / (1024 * 1024)), (avail / (1024 * 1024))))
return (ret, msg)
def is_conflict_disk(self, conn, return_names=False):
"""
check if specified storage is in use by any other VMs on passed
connection.
@param conn: connection to check for collisions on
@type conn: libvirt.virConnect
@param return_names: Whether or not to return a list of VM names using
the same storage (default = False)
@type return_names: C{bool}
@return: True if a collision, False otherwise (list of names if
return_names passed)
@rtype: C{bool}
"""
if self.vol_object:
path = self.vol_object.path()
else:
path = self.path
if not path:
return False
if not conn:
conn = self.conn
check_conflict = self.shareable
names = self.path_in_use_by(conn, path,
check_conflict=check_conflict)
ret = False
if names:
ret = True
if return_names:
ret = names
return ret
def _support_selinux(self):
"""
Return True if we have the requisite libvirt and library support
for selinux commands
"""
caps = self._get_caps()
if not caps:
return False
elif "selinux" not in [x.model for x in caps.host.secmodels]:
return False
elif self.is_remote():
return False
elif not util.have_selinux():
# XXX: When libvirt supports changing labels via storage APIs,
# this will need changing.
return False
elif self.__managed_storage() and self.path:
try:
statinfo = os.stat(self.path)
except:
return False
# Not sure if this is even the correct metric for
# 'Can we change the file context'
return os.geteuid() in ['0', statinfo.st_uid]
return True
def _expected_security_label(self):
"""
Best guess at what the expected selinux label should be for the disk
"""
label = None
# XXX: These are really only approximations in the remote case?
# XXX: Maybe libvirt should expose the relevant selinux labels in
# the capabilities XML?
if not self._support_selinux():
pass
elif self.__no_storage():
pass
elif self.read_only:
label = util.selinux_readonly_label()
elif self.shareable:
# XXX: Should this be different? or do we not care about MLS here?
label = util.selinux_rw_label()
else:
label = util.selinux_rw_label()
return label or ""
def _security_can_fix(self):
can_fix = True
if not self._support_selinux():
can_fix = False
elif self.__no_storage():
can_fix = False
elif self.type == VirtualDisk.TYPE_BLOCK:
# Shouldn't change labelling on block devices (though we can)
can_fix = False
elif not self.read_only:
# XXX Leave all other (R/W disk) relabeling up to libvirt/svirt
# for now
can_fix = False
return can_fix
def get_target_prefix(self):
"""
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.
if self.bus == "virtio":
return ("vd", 1024)
elif self.bus in ["sata", "scsi", "usb"]:
return ("sd", 1024)
elif self.bus == "xen":
return ("xvd", 1024)
elif self.bus == "fdc" or self.device == self.DEVICE_FLOPPY:
return ("fd", 2)
elif self.bus == "ide":
return ("hd", 4)
else:
return (None, None)
def generate_target(self, skip_targets):
"""
Generate target device ('hda', 'sdb', etc..) for disk, excluding
any targets in 'skip_targets'. Sets self.target, and returns the
generated value
@param skip_targets: list of targets to exclude
@type skip_targets: C{list}
@raise ValueError: can't determine target type, no targets available
@returns generated target
@rtype C{str}
"""
# Only use these targets if there are no other options
except_targets = ["hdc"]
prefix, maxnode = self.get_target_prefix()
if prefix is None:
raise ValueError(_("Cannot determine device bus/type."))
# Special case: IDE cdrom should prefer hdc for back compat
if self.device == self.DEVICE_CDROM and prefix == "hd":
if "hdc" not in skip_targets:
self.target = "hdc"
return self.target
if maxnode > (26 * 26 * 26):
raise RuntimeError("maxnode value is too high")
# Regular scanning
for i in range(1, maxnode + 1):
gen_t = prefix
tmp = i
digits = []
for factor in range(0, 3):
amt = (tmp % (26 ** (factor + 1))) / (26 ** factor)
if amt == 0 and tmp >= (26 ** (factor + 1)):
amt = 26
tmp -= amt
digits.insert(0, amt)
seen_valid = False
for digit in digits:
if digit == 0:
if not seen_valid:
continue
digit = 1
seen_valid = True
gen_t += "%c" % (ord('a') + digit - 1)
if gen_t in except_targets:
continue
if gen_t not in skip_targets:
self.target = gen_t
return self.target
# Check except_targets for any options
for t in except_targets:
if t.startswith(prefix) and t not in skip_targets:
self.target = t
return self.target
raise ValueError(_("No more space for disks of type '%s'" % prefix))