virt-manager/virtinst/devicedisk.py

969 lines
31 KiB
Python

#
# Classes for building disk device xml
#
# Copyright 2006-2008, 2012-2014 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 subprocess
import logging
import re
import urlgrabber.progress as progress
from . import diskbackend
from . import util
from .device import VirtualDevice
from .xmlbuilder import XMLProperty
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 _distill_storage(conn, do_create, nomanaged,
path, vol_object, vol_install,
clone_path, backing_store,
*args):
"""
Validates and updates params when the backing storage is changed
"""
pool = None
path_is_pool = False
storage_capable = conn.check_support(conn.SUPPORT_CONN_STORAGE)
if vol_object:
pass
elif not storage_capable:
pass
elif path and not nomanaged:
path = os.path.abspath(path)
(vol_object, pool, path_is_pool) = diskbackend.manage_path(conn, path)
creator = None
backend = diskbackend.StorageBackend(conn, path, vol_object,
path_is_pool and pool or None)
if not do_create:
return backend, None
if backend.exists(auto_check=False) and path is not None:
if not clone_path:
return backend, None
if path and not (vol_install or pool or clone_path):
raise RuntimeError(_("Don't know how to create storage for "
"path '%s'. Use libvirt APIs to manage the parent directory "
"as a pool first.") % path)
if path or vol_install or pool or clone_path:
creator = diskbackend.StorageCreator(conn, path, pool,
vol_install, clone_path,
backing_store, *args)
return backend, creator
_TARGET_PROPS = ["file", "dev", "dir"]
class VirtualDisk(VirtualDevice):
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_MODE_DIRECTSYNC = "directsync"
CACHE_MODE_UNSAFE = "unsafe"
cache_types = [CACHE_MODE_NONE, CACHE_MODE_WRITETHROUGH,
CACHE_MODE_WRITEBACK, CACHE_MODE_DIRECTSYNC, CACHE_MODE_UNSAFE]
DISCARD_MODE_IGNORE = "ignore"
DISCARD_MODE_UNMAP = "unmap"
discard_types = [DISCARD_MODE_IGNORE, DISCARD_MODE_UNMAP]
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]
IO_MODE_NATIVE = "native"
IO_MODE_THREADS = "threads"
io_modes = [IO_MODE_NATIVE, IO_MODE_THREADS]
error_policies = ["ignore", "stop", "enospace", "report"]
@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"
@staticmethod
def path_exists(conn, path):
"""
Check if path exists. If we can't determine, return False
"""
if path is None:
return False
try:
(vol, pool, path_is_pool) = diskbackend.check_if_path_managed(
conn, path)
ignore = pool
if vol or path_is_pool:
return True
if not conn.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 conn.is_remote():
return []
if username == "root":
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 check_path_search(conn, path):
# Only works for qemu and DAC
if conn.is_remote() or not conn.is_qemu_system():
return None, []
from virtcli import cliconfig
user = cliconfig.default_qemu_user
try:
for i in conn.caps.host.secmodels:
if i.model != "dac":
continue
label = (i.baselabels.get("kvm") or
i.baselabels.get("qemu"))
if not label:
continue
pwuid = pwd.getpwuid(
int(label.split(":")[0].replace("+", "")))
if pwuid:
user = pwuid[0]
except:
logging.debug("Exception grabbing qemu DAC user", exc_info=True)
return None, []
return user, VirtualDisk.check_path_search_for_user(conn, path, user)
@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, shareable=False, read_only=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 shareable: Path we are checking is marked shareable, so
don't warn if it conflicts with another shareable source.
@param read_only: Path we are checking is marked read_only, so
don't warn if it conflicts with another read_only source.
"""
if not path:
return []
# Find all volumes that have 'path' somewhere in their backing chain
vols = []
volmap = dict((vol.backing_store, vol)
for vol in conn.fetch_all_vols() if vol.backing_store)
backpath = path
while backpath in volmap:
vol = volmap[backpath]
if vol in vols:
break
backpath = vol.target_path
vols.append(backpath)
ret = []
vms = conn.fetch_all_guests()
for vm in vms:
if not read_only:
if path in [vm.os.kernel, vm.os.initrd, vm.os.dtb]:
ret.append(vm.name)
continue
for disk in vm.get_devices("disk"):
if disk.path in vols and vm.name not in ret:
# VM uses the path indirectly via backing store
ret.append(vm.name)
break
if disk.path != path:
continue
if shareable and disk.shareable:
continue
if read_only and disk.read_only:
continue
ret.append(vm.name)
break
return ret
@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 a pool name, vol name tuple
"""
if not conn.check_support(conn.SUPPORT_CONN_STORAGE):
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)))
@staticmethod
def build_vol_install(*args, **kwargs):
return diskbackend.build_vol_install(*args, **kwargs)
@staticmethod
def num_to_target(num):
"""
Convert an index in range (1, 1024) to a disk /dev number
(like hda, hdb, hdaa, etc.)
"""
digits = []
for factor in range(0, 3):
amt = (num % (26 ** (factor + 1))) / (26 ** factor)
if amt == 0 and num >= (26 ** (factor + 1)):
amt = 26
num -= amt
digits.insert(0, amt)
seen_valid = False
gen_t = ""
for digit in digits:
if digit == 0:
if not seen_valid:
continue
digit = 1
seen_valid = True
gen_t += "%c" % (ord('a') + digit - 1)
return gen_t
@staticmethod
def target_to_num(tgt):
"""
Convert disk /dev number (like hda, hdb, hdaa, etc.) to an index
"""
num = 0
k = 0
if tgt[0] == 'x':
# This case is here for 'xvda'
tgt = tgt[1:]
for i, c in enumerate(reversed(tgt[2:])):
if i != 0:
k = 1
num += (ord(c) - ord('a') + k) * (26 ** i)
return num
_XML_PROP_ORDER = [
"type", "device",
"driver_name", "driver_type",
"driver_cache", "driver_discard", "driver_io", "error_policy",
"_xmlpath", "target", "bus",
]
def __init__(self, *args, **kwargs):
VirtualDevice.__init__(self, *args, **kwargs)
self.__storage_backend = None
self._storage_creator = None
self.nomanaged = False
self.transient = False
#############################
# Public property-esque API #
#############################
def _get_path(self):
if self._storage_creator:
return self._storage_creator.path
return self._storage_backend.path
def _set_path(self, val):
if self._storage_creator:
raise ValueError("Can't change disk path if storage creation info "
"has been set.")
self._change_backend(val, None)
self._xmlpath = self.path
path = property(_get_path, _set_path)
def get_sparse(self):
if self._storage_creator:
return self._storage_creator.get_sparse()
return None
def get_vol_object(self):
return self._storage_backend.get_vol_object()
def get_vol_install(self):
if not self._storage_creator:
return None
return self._storage_creator.get_vol_install()
def get_size(self):
if self._storage_creator:
return self._storage_creator.get_size()
return self._storage_backend.get_size()
#############################
# Internal defaults helpers #
#############################
def _get_default_type(self):
if self._storage_creator:
return self._storage_creator.get_dev_type()
return self._storage_backend.get_dev_type()
def _get_default_driver_name(self):
if not self.path:
return None
if self.conn.is_qemu():
return self.DRIVER_QEMU
return None
def _get_default_driver_type(self):
"""
Set driver type from passed parameters
Where possible, we want to force /driver/@type = "raw" if installing
a QEMU VM. Without telling QEMU to expect a raw file, the emulator
is forced to autodetect, which has security implications:
http://lists.gnu.org/archive/html/qemu-devel/2008-04/msg00675.html
"""
if self.driver_name != self.DRIVER_QEMU:
return None
if self._storage_creator:
drvtype = self._storage_creator.get_driver_type()
else:
drvtype = self._storage_backend.get_driver_type()
return _qemu_sanitize_drvtype(self.type, drvtype)
##################
# XML properties #
##################
def _make_source_xpath(self):
return "./source/@" + self.disk_type_to_target_prop(self.type)
_xmlpath = XMLProperty(name="disk path",
make_xpath_cb=_make_source_xpath,
clear_first=["./source/@" + target for target in
_TARGET_PROPS])
sourceStartupPolicy = XMLProperty("./source/@startupPolicy")
device = XMLProperty("./@device",
default_cb=lambda s: s.DEVICE_DISK)
type = XMLProperty("./@type", default_cb=_get_default_type)
driver_name = XMLProperty("./driver/@name",
default_cb=_get_default_driver_name)
driver_type = XMLProperty("./driver/@type",
default_cb=_get_default_driver_type)
bus = XMLProperty("./target/@bus")
target = XMLProperty("./target/@dev")
removable = XMLProperty("./target/@removable", is_onoff=True)
read_only = XMLProperty("./readonly", is_bool=True)
shareable = XMLProperty("./shareable", is_bool=True)
driver_cache = XMLProperty("./driver/@cache")
driver_discard = XMLProperty("./driver/@discard")
driver_io = XMLProperty("./driver/@io")
error_policy = XMLProperty("./driver/@error_policy")
serial = XMLProperty("./serial")
iotune_rbs = XMLProperty("./iotune/read_bytes_sec", is_int=True)
iotune_ris = XMLProperty("./iotune/read_iops_sec", is_int=True)
iotune_tbs = XMLProperty("./iotune/total_bytes_sec", is_int=True)
iotune_tis = XMLProperty("./iotune/total_iops_sec", is_int=True)
iotune_wbs = XMLProperty("./iotune/write_bytes_sec", is_int=True)
iotune_wis = XMLProperty("./iotune/write_iops_sec", is_int=True)
#################################
# Validation assistance methods #
#################################
def _get_storage_backend(self):
if self.__storage_backend is None:
self.__storage_backend = diskbackend.StorageBackend(self.conn,
self._xmlpath,
None, None)
return self.__storage_backend
def _set_storage_backend(self, val):
self.__storage_backend = val
_storage_backend = property(_get_storage_backend, _set_storage_backend)
def set_create_storage(self, size=None, sparse=True,
fmt=None, vol_install=None,
clone_path=None, backing_store=None,
fake=False):
"""
Function that sets storage creation parameters. If this isn't
called, we assume that no storage creation is taking place and
will error accordingly.
@size is in gigs
@fake: If true, make like we are creating storage but fail
if we ever asked to do so.
"""
def _validate_path(p):
if p is None:
return
try:
d = VirtualDisk(self.conn)
d.path = p
# If this disk isn't managed, make sure we only perform
# non-managed lookup.
if (self._storage_creator or
(self.path and self._storage_backend.exists())):
d.nomanaged = not self.__managed_storage()
d.set_create_storage(fake=True)
d.validate()
except Exception, e:
raise ValueError(_("Error validating path %s: %s") % (p, e))
path = self.path
# Validate clone_path
if clone_path is not None:
clone_path = os.path.abspath(clone_path)
if backing_store is not None:
backing_store = os.path.abspath(backing_store)
if not fake:
_validate_path(clone_path)
_validate_path(backing_store)
if fake and size is None:
size = .000001
ignore, creator = _distill_storage(
self.conn, True, self.nomanaged, path, None,
vol_install, clone_path, backing_store,
size, sparse, fmt)
self._storage_creator = creator
if self._storage_creator:
self._storage_creator.fake = bool(fake)
self._xmlpath = self.path
else:
if (vol_install or clone_path):
raise RuntimeError("Need storage creation but it "
"didn't happen.")
if fmt and self.driver_name == self.DRIVER_QEMU:
self.driver_type = fmt
def is_cdrom(self):
return self.device == self.DEVICE_CDROM
def is_floppy(self):
return self.device == self.DEVICE_FLOPPY
def is_disk(self):
return self.device == self.DEVICE_DISK
def can_be_empty(self):
return self.is_floppy() or self.is_cdrom()
def _change_backend(self, path, vol_object):
backend, ignore = _distill_storage(
self.conn, False, self.nomanaged,
path, vol_object, None, None, None)
self._storage_backend = backend
def sync_path_props(self):
"""
Fills in the values of type, driver_type, and driver_name for
the associated backing storage. This needs to be manually called
if changing an existing disk's media.
"""
self.type = self._get_default_type()
self.driver_name = self._get_default_driver_name()
self.driver_type = self._get_default_driver_type()
def __managed_storage(self):
"""
Return bool representing if managed storage parameters have
been explicitly specified or filled in
"""
if self._storage_creator:
return self._storage_creator.is_managed()
return self._storage_backend.is_managed()
def creating_storage(self):
"""
Return True if the user requested us to create a device
"""
return bool(self._storage_creator)
def validate(self):
"""
function to validate all the complex interaction between the various
disk parameters.
"""
# No storage specified for a removable device type (CDROM, floppy)
if self.path is None:
if not self.can_be_empty():
raise ValueError(_("Device type '%s' requires a path") %
self.device)
return True
storage_capable = self.conn.check_support(
self.conn.SUPPORT_CONN_STORAGE)
if self.conn.is_remote():
if not storage_capable:
raise ValueError(_("Connection doesn't support remote "
"storage."))
# 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()
# If not creating the storage, our job is easy
if not create_media:
if not self._storage_backend.exists():
raise ValueError(
_("Must specify storage creation parameters for "
"non-existent path '%s'.") % self.path)
# Make sure we have access to the local path
if not managed_storage:
if (os.path.isdir(self.path) and not self.is_floppy()):
raise ValueError(_("The path '%s' must be a file or a "
"device, not a directory") % self.path)
return True
self._storage_creator.validate(self.device, self.type)
# 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])
def setup(self, 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 meter: Progress meter to report file creation on
@type meter: instanceof urlgrabber.BaseMeter
"""
if not meter:
meter = progress.BaseMeter()
if not self._storage_creator:
return
volobj = self._storage_creator.create(meter)
self._storage_creator = None
if volobj:
self._change_backend(None, volobj)
def set_defaults(self, guest):
if self.is_cdrom():
self.read_only = True
if (guest.os.is_xenpv() and
self.type == VirtualDisk.TYPE_FILE and
self.driver_name is None and
util.is_blktap_capable(self.conn)):
self.driver_name = VirtualDisk.DRIVER_TAP
if not self.conn.is_qemu():
return
if not self.is_disk():
return
if not self.type == self.TYPE_BLOCK:
return
# Enable cache=none and io=native for block devices. Would
# be nice if qemu did this for us but that time has long passed.
if not self.driver_cache:
self.driver_cache = self.CACHE_MODE_NONE
if not self.driver_io:
self.driver_io = self.IO_MODE_NATIVE
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 not self._storage_creator:
return (False, None)
return self._storage_creator.is_size_conflict()
def is_conflict_disk(self, conn=None):
"""
check if specified storage is in use by any other VMs on passed
connection.
@return: list of colliding VM names
@rtype: C{list}
"""
if not self.path:
return False
if not conn:
conn = self.conn
ret = self.path_in_use_by(conn, self.path,
shareable=self.shareable,
read_only=self.read_only)
return ret
def get_target_prefix(self, used_targets=None):
"""
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.
def _return(prefix):
nummap = {
"vd": 1024,
"xvd": 1024,
"fd": 2,
"hd": 4,
"sd": 1024,
}
return prefix, nummap[prefix]
if self.bus == "virtio":
return _return("vd")
elif self.bus == "xen":
return _return("xvd")
elif self.bus == "fdc" or self.is_floppy():
return _return("fd")
elif self.bus == "ide":
return _return("hd")
elif self.bus or not used_targets:
# sata, scsi, usb, sd
return _return("sd")
# If guest already has some disks defined
preforder = ["vd", "xvd", "sd", "hd"]
for pref in preforder:
for target in used_targets:
if target.startswith(pref):
return _return(pref)
return _return("sd")
def generate_target(self, skip_targets, pref_ctrl=None):
"""
Generate target device ('hda', 'sdb', etc..) for disk, excluding
any targets in 'skip_targets'. If given the 'pref_ctrl'
parameter, it tries to select the target so that the disk is
mapped onto that controller.
Sets self.target, and returns the generated value.
@param skip_targets: list of targets to exclude
@type skip_targets: C{list}
@param pref_ctrl: preferred controller to connect the disk to
@type pref_ctrl: C{int}
@raise ValueError: can't determine target type, no targets available
@returns generated target
@rtype C{str}
"""
prefix, maxnode = self.get_target_prefix(skip_targets)
skip_targets = [t for t in skip_targets if t and t.startswith(prefix)]
skip_targets.sort()
def get_target():
first_found = None
ran = range(maxnode)
if pref_ctrl is not None:
# We assume narrow SCSI bus and libvirt assigning 7
# (1-7, 8-14, etc.) devices per controller
ran = range(pref_ctrl * 7, (pref_ctrl + 1) * 7)
for i in ran:
gen_t = prefix + self.num_to_target(i + 1)
if gen_t in skip_targets:
skip_targets.remove(gen_t)
continue
if not skip_targets:
return gen_t
elif not first_found:
first_found = gen_t
if first_found:
return first_found
ret = get_target()
if ret:
self.target = ret
return ret
if pref_ctrl is not None:
# This basically means that we either chose full
# controller or didn't add any
raise ValueError(_("Controller number %d for disk of type %s has "
"no empty slot to use" % (pref_ctrl, prefix)))
else:
raise ValueError(_("Only %s disks of type '%s' are supported"
% (maxnode, prefix)))
VirtualDisk.register_type()