virt-manager/virtinst/VirtualDisk.py

1884 lines
66 KiB
Python
Raw Normal View History

#
# 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))
class XenDisk(VirtualDisk):
"""
Back compat class to avoid ABI break.
"""
pass