mirror of
https://github.com/virt-manager/virt-manager.git
synced 2025-01-08 21:18:04 +03:00
1884 lines
66 KiB
Python
1884 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))
|
||
|
|
||
|
|
||
|
class XenDisk(VirtualDisk):
|
||
|
"""
|
||
|
Back compat class to avoid ABI break.
|
||
|
"""
|
||
|
pass
|