1#
2# Classes for building disk device xml
3#
4# Copyright 2006-2008, 2012-2014 Red Hat, Inc.
5#
6# This work is licensed under the GNU GPLv2 or later.
7# See the COPYING file in the top-level directory.
8
9import os
10
11from ..logger import log
12
13from .. import diskbackend
14from .. import progress
15from .. import xmlutil
16from .device import Device, DeviceAddress, DeviceSeclabel
17from ..xmlbuilder import XMLBuilder, XMLChildProperty, XMLProperty
18
19
20def _qemu_sanitize_drvtype(phystype, fmt):
21    """
22    Sanitize libvirt storage volume format to a valid qemu driver type
23    """
24    raw_list = ["iso"]
25
26    if phystype == DeviceDisk.TYPE_BLOCK:
27        return DeviceDisk.DRIVER_TYPE_RAW
28    if fmt in raw_list:
29        return DeviceDisk.DRIVER_TYPE_RAW
30    return fmt
31
32
33class _Host(XMLBuilder):
34    _XML_PROP_ORDER = ["name", "port", "transport", "socket"]
35    XML_NAME = "host"
36
37    name = XMLProperty("./@name")
38    port = XMLProperty("./@port", is_int=True)
39    transport = XMLProperty("./@transport")
40    socket = XMLProperty("./@socket")
41
42
43class _DiskSourceAddress(DeviceAddress):
44    pass
45
46
47class _DiskSource(XMLBuilder):
48    """
49    Class representing disk <source> block, and various helpers
50    that only operate on <source> contents
51    """
52    _XML_PROP_ORDER = [
53        "file", "dev", "dir",
54        "volume", "pool", "protocol", "name", "hosts",
55        "type", "managed", "namespace", "address"]
56    XML_NAME = "source"
57
58    file = XMLProperty("./@file")
59    dev = XMLProperty("./@dev")
60    dir = XMLProperty("./@dir")
61
62    pool = XMLProperty("./@pool")
63    volume = XMLProperty("./@volume")
64
65    hosts = XMLChildProperty(_Host)
66
67    name = XMLProperty("./@name")
68    protocol = XMLProperty("./@protocol")
69
70    type = XMLProperty("./@type")
71    managed = XMLProperty("./@managed", is_yesno=True)
72    namespace = XMLProperty("./@namespace", is_int=True)
73    address = XMLChildProperty(_DiskSourceAddress, is_single=True)
74
75    def set_from_url(self, uri):
76        """
77        For a passed in path URI like gluster:// or https://, split it
78        up and set the <disk> properties directly
79        """
80        from ..uri import URI
81        uriobj = URI(uri)
82
83        if uriobj.scheme:
84            self.protocol = uriobj.scheme
85        if ((uriobj.hostname or uriobj.port or uriobj.transport) and
86            not self.hosts):
87            self.hosts.add_new()
88        if uriobj.transport:
89            self.hosts[0].transport = uriobj.transport
90        if uriobj.hostname:
91            self.hosts[0].name = uriobj.hostname
92        if uriobj.port:
93            self.hosts[0].port = uriobj.port
94        if uriobj.path:
95            if self.hosts and self.hosts[0].transport:
96                self.hosts[0].socket = uriobj.path
97            else:
98                self.name = uriobj.path
99                if self.name.startswith("/"):
100                    self.name = self.name[1:]
101
102    def build_url_from_network(self):
103        """
104        Build a URL from network contents of <source>
105        """
106        host = _Host(self.conn)
107        if self.hosts:
108            host = self.hosts[0]
109
110        ret = self.protocol or "unknown"
111        if host.transport:
112            ret += "+%s" % host.transport
113        ret += "://"
114        if host.name:
115            ret += host.name
116            if host.port:
117                ret += ":" + str(host.port)
118        if self.name:
119            if not self.name.startswith("/"):
120                ret += "/"
121            ret += self.name
122        elif host.socket:
123            if not host.socket.startswith("/"):
124                ret += "/"
125            ret += host.socket
126        return ret
127
128    def clear_source(self):
129        """
130        Unset all XML properties that describe the actual source media
131        """
132        self.file = None
133        self.dev = None
134        self.dir = None
135        self.volume = None
136        self.pool = None
137        self.name = None
138        self.protocol = None
139        for h in self.hosts[:]:
140            self.remove_child(h)
141
142    def set_network_from_storage(self, volxml, poolxml):
143        """
144        For the passed pool + vol object combo representing a network
145        volume, set the <source> elements directly
146        """
147        is_iscsi_direct = poolxml.type == "iscsi-direct"
148        protocol = poolxml.type
149        if is_iscsi_direct:
150            protocol = "iscsi"
151
152        self.protocol = protocol
153        for idx, poolhost in enumerate(poolxml.hosts):
154            if len(self.hosts) < (idx + 1):
155                self.hosts.add_new()
156            self.hosts[idx].name = poolhost.name
157            self.hosts[idx].port = poolhost.port
158
159        path = ""
160        if is_iscsi_direct:
161            # Vol path is like this:
162            # ip-10.66.144.87:3260-iscsi-iqn.2017-12.com.virttest:emulated-iscsi-noauth.target2-lun-1
163            # Always seems to have -iscsi- embedded in it
164            if "-iscsi-iqn." in volxml.target_path:
165                path = volxml.target_path.split("-iscsi-", 1)[-1]
166        else:
167            if poolxml.source_name:
168                path += poolxml.source_name
169                if poolxml.source_path:
170                    path += poolxml.source_path
171                if not path.endswith('/'):
172                    path += "/"
173            path += volxml.name
174        self.name = path or None
175
176
177class DeviceDisk(Device):
178    XML_NAME = "disk"
179
180    DRIVER_NAME_PHY = "phy"
181    DRIVER_NAME_QEMU = "qemu"
182    DRIVER_TYPE_RAW = "raw"
183
184    CACHE_MODE_NONE = "none"
185    CACHE_MODE_WRITETHROUGH = "writethrough"
186    CACHE_MODE_WRITEBACK = "writeback"
187    CACHE_MODE_DIRECTSYNC = "directsync"
188    CACHE_MODE_UNSAFE = "unsafe"
189    CACHE_MODES = [CACHE_MODE_NONE, CACHE_MODE_WRITETHROUGH,
190        CACHE_MODE_WRITEBACK, CACHE_MODE_DIRECTSYNC, CACHE_MODE_UNSAFE]
191
192    DISCARD_MODE_IGNORE = "ignore"
193    DISCARD_MODE_UNMAP = "unmap"
194    DISCARD_MODES = [DISCARD_MODE_IGNORE, DISCARD_MODE_UNMAP]
195
196    DETECT_ZEROES_MODE_OFF = "off"
197    DETECT_ZEROES_MODE_ON = "on"
198    DETECT_ZEROES_MODE_UNMAP = "unmap"
199    DETECT_ZEROES_MODES = [DETECT_ZEROES_MODE_OFF, DETECT_ZEROES_MODE_ON,
200                           DETECT_ZEROES_MODE_UNMAP]
201
202    DEVICE_DISK = "disk"
203    DEVICE_LUN = "lun"
204    DEVICE_CDROM = "cdrom"
205    DEVICE_FLOPPY = "floppy"
206
207    TYPE_FILE = "file"
208    TYPE_BLOCK = "block"
209    TYPE_DIR = "dir"
210    TYPE_VOLUME = "volume"
211    TYPE_NETWORK = "network"
212
213    IO_MODE_NATIVE = "native"
214
215
216    @staticmethod
217    def path_definitely_exists(conn, path):
218        """
219        Check if path exists.
220
221        return True if we are certain, False otherwise. Path may in fact
222        exist if we return False, but we can't exhaustively know in all
223        cases.
224
225        (In fact if cached storage volume data is out of date, the volume
226         may have disappeared behind our back, but that shouldn't have bad
227         effects in practice.)
228        """
229        return diskbackend.path_definitely_exists(conn, path)
230
231    @staticmethod
232    def check_path_search(conn, path):
233        """
234        Check if the connection DAC user has search permissions for all the
235        directories in the passed path.
236
237        :returns: Class with:
238            - List of the directories the user cannot search, or empty list
239            - username we checked for or None if not applicable
240            - uid we checked for or None if not application
241        """
242        log.debug("DeviceDisk.check_path_search path=%s", path)
243        class SearchData(object):
244            def __init__(self):
245                self.user = None
246                self.uid = None
247                self.fixlist = []
248
249        searchdata = SearchData()
250        if not path:
251            return searchdata
252
253        if conn.is_remote():
254            return searchdata
255        if not conn.is_qemu_privileged():
256            return searchdata
257        if diskbackend.path_is_url(path):
258            return searchdata
259        if diskbackend.path_is_network_vol(conn, path):
260            return searchdata
261        path = os.path.abspath(path)
262
263        user, uid = conn.caps.host.get_qemu_baselabel()
264        if not user:
265            return searchdata
266        if uid == 0 and not xmlutil.in_testsuite():
267            return searchdata  # pragma: no cover
268
269        searchdata.user = user
270        searchdata.uid = uid
271        searchdata.fixlist = diskbackend.is_path_searchable(path, uid, user)
272        searchdata.fixlist.reverse()
273        return searchdata
274
275    @staticmethod
276    def fix_path_search(searchdata):
277        """
278        Try to fix any permission problems found by check_path_search
279
280        :returns: Return a dictionary of entries {broken path : error msg}
281        """
282        errdict = diskbackend.set_dirs_searchable(
283                searchdata.fixlist, searchdata.user)
284        return errdict
285
286    @staticmethod
287    def path_in_use_by(conn, path, shareable=False, read_only=False):
288        """
289        Return a list of VM names that are using the passed path.
290
291        :param conn: virConnect to check VMs
292        :param path: Path to check for
293        :param shareable: Path we are checking is marked shareable, so
294            don't warn if it conflicts with another shareable source.
295        :param read_only: Path we are checking is marked read_only, so
296            don't warn if it conflicts with another read_only source.
297        """
298        if not path:
299            return []
300
301        # Find all volumes that have 'path' somewhere in their backing chain
302        vols = []
303        volmap = dict((vol.backing_store, vol)
304                      for vol in conn.fetch_all_vols() if vol.backing_store)
305        backpath = path
306        while backpath in volmap:
307            vol = volmap[backpath]
308            if vol in vols:
309                break  # pragma: no cover
310            backpath = vol.target_path
311            vols.append(backpath)
312
313        ret = []
314        vms = conn.fetch_all_domains()
315        for vm in vms:
316            if not read_only:
317                if path in [vm.os.kernel, vm.os.initrd, vm.os.dtb]:
318                    ret.append(vm.name)
319                    continue
320
321            for disk in vm.devices.disk:
322                checkpath = disk.get_source_path()
323                if checkpath in vols and vm.name not in ret:
324                    # VM uses the path indirectly via backing store
325                    ret.append(vm.name)
326                    break
327
328                if checkpath != path:
329                    continue
330
331                if shareable and disk.shareable:
332                    continue
333                if read_only and disk.read_only:
334                    continue
335
336                ret.append(vm.name)
337                break
338
339        return ret
340
341    @staticmethod
342    def build_vol_install(conn, volname, poolobj, size, sparse,
343                          fmt=None, backing_store=None, backing_format=None):
344        """
345        Helper for building a StorageVolume instance to pass to DeviceDisk
346        for eventual storage creation.
347
348        :param volname: name of the volume to be created
349        :param size: size in bytes
350        """
351        from ..storage import StorageVolume
352
353        if size is None:
354            raise ValueError(_("Size must be specified for non "
355                               "existent volume '%s'" % volname))
356
357        # This catches --disk /dev/idontexist,size=1 if /dev is unmanaged
358        if not poolobj:
359            raise RuntimeError(_("Don't know how to create storage for "
360                "path '%s'. Use libvirt APIs to manage the parent directory "
361                "as a pool first.") % volname)
362
363        log.debug("Creating volume '%s' on pool '%s'",
364                      volname, poolobj.name())
365
366        cap = (size * 1024 * 1024 * 1024)
367        if sparse:
368            alloc = 0
369        else:
370            alloc = cap
371
372        volinst = StorageVolume(conn)
373        volinst.pool = poolobj
374        volinst.name = volname
375        volinst.capacity = cap
376        volinst.allocation = alloc
377        volinst.backing_store = backing_store
378        volinst.backing_format = backing_format
379
380        if fmt:
381            if not volinst.supports_format():
382                raise ValueError(_("Format attribute not supported for this "
383                                   "volume type"))
384            volinst.format = fmt
385
386        return volinst
387
388    @staticmethod
389    def num_to_target(num):
390        """
391        Convert an index in range (1, 1024) to a disk /dev number
392        (like hda, hdb, hdaa, etc.)
393        """
394        digits = []
395        for factor in range(0, 3):
396            amt = (num % (26 ** (factor + 1))) // (26 ** factor)
397            if amt == 0 and num >= (26 ** (factor + 1)):
398                amt = 26
399            num -= amt
400            digits.insert(0, amt)
401
402        gen_t = ""
403        for digit in digits:
404            if digit == 0:
405                continue
406            gen_t += "%c" % (ord('a') + digit - 1)
407
408        return gen_t
409
410
411    @staticmethod
412    def target_to_num(tgt):
413        """
414        Convert disk /dev number (like hda, hdb, hdaa, etc.) to an index
415        """
416        num = 0
417        k = 0
418        if tgt[0] == 'x':
419            # This case is here for 'xvda'
420            tgt = tgt[1:]
421        for i, c in enumerate(reversed(tgt[2:])):
422            if i != 0:
423                k = 1
424            num += (ord(c) - ord('a') + k) * (26 ** i)
425        return num
426
427
428    _XML_PROP_ORDER = [
429        "_xmltype", "_device", "snapshot_policy",
430        "driver_name", "driver_type",
431        "driver_cache", "driver_discard", "driver_detect_zeroes",
432        "driver_io", "error_policy",
433        "auth_username", "auth_secret_type", "auth_secret_uuid",
434        "source",
435        "target", "bus",
436    ]
437
438    def __init__(self, *args, **kwargs):
439        Device.__init__(self, *args, **kwargs)
440
441        self._source_volume_err = None
442        self.storage_was_created = False
443
444        self._storage_backend = diskbackend.StorageBackendStub(
445            self.conn, self._get_xmlpath(), self._xmltype, self.driver_type)
446
447
448    ##################
449    # XML properties #
450    ##################
451
452    _xmltype = XMLProperty("./@type")
453    _device = XMLProperty("./@device")
454
455    driver_name = XMLProperty("./driver/@name")
456    driver_type = XMLProperty("./driver/@type")
457
458    source = XMLChildProperty(_DiskSource, is_single=True)
459
460    auth_username = XMLProperty("./auth/@username")
461    auth_secret_type = XMLProperty("./auth/secret/@type")
462    auth_secret_uuid = XMLProperty("./auth/secret/@uuid")
463
464    snapshot_policy = XMLProperty("./@snapshot")
465
466    driver_copy_on_read = XMLProperty("./driver/@copy_on_read", is_onoff=True)
467
468    sgio = XMLProperty("./@sgio")
469    rawio = XMLProperty("./@rawio")
470
471    bus = XMLProperty("./target/@bus")
472    target = XMLProperty("./target/@dev")
473    removable = XMLProperty("./target/@removable", is_onoff=True)
474
475    read_only = XMLProperty("./readonly", is_bool=True)
476    shareable = XMLProperty("./shareable", is_bool=True)
477    driver_cache = XMLProperty("./driver/@cache")
478    driver_discard = XMLProperty("./driver/@discard")
479    driver_detect_zeroes = XMLProperty("./driver/@detect_zeroes")
480    driver_io = XMLProperty("./driver/@io")
481    driver_iothread = XMLProperty("./driver/@iothread", is_int=True)
482
483    error_policy = XMLProperty("./driver/@error_policy")
484    serial = XMLProperty("./serial")
485    wwn = XMLProperty("./wwn")
486    startup_policy = XMLProperty("./source/@startupPolicy")
487    logical_block_size = XMLProperty("./blockio/@logical_block_size")
488    physical_block_size = XMLProperty("./blockio/@physical_block_size")
489
490    iotune_rbs = XMLProperty("./iotune/read_bytes_sec", is_int=True)
491    iotune_ris = XMLProperty("./iotune/read_iops_sec", is_int=True)
492    iotune_tbs = XMLProperty("./iotune/total_bytes_sec", is_int=True)
493    iotune_tis = XMLProperty("./iotune/total_iops_sec", is_int=True)
494    iotune_wbs = XMLProperty("./iotune/write_bytes_sec", is_int=True)
495    iotune_wis = XMLProperty("./iotune/write_iops_sec", is_int=True)
496
497    seclabels = XMLChildProperty(DeviceSeclabel, relative_xpath="./source")
498
499    geometry_cyls = XMLProperty("./geometry/@cyls", is_int=True)
500    geometry_heads = XMLProperty("./geometry/@heads", is_int=True)
501    geometry_secs = XMLProperty("./geometry/@secs", is_int=True)
502    geometry_trans = XMLProperty("./geometry/@trans")
503
504    reservations_managed = XMLProperty("./source/reservations/@managed")
505    reservations_source_type = XMLProperty("./source/reservations/source/@type")
506    reservations_source_path = XMLProperty("./source/reservations/source/@path")
507    reservations_source_mode = XMLProperty("./source/reservations/source/@mode")
508
509
510    #############################
511    # Internal defaults helpers #
512    #############################
513
514    def _get_default_type(self):
515        if self.source.pool or self.source.volume:
516            return DeviceDisk.TYPE_VOLUME
517        if not self._storage_backend.is_stub():
518            return self._storage_backend.get_dev_type()
519        if self.source.protocol:
520            return DeviceDisk.TYPE_NETWORK
521        return self.TYPE_FILE
522
523    def _get_default_driver_name(self):
524        if self.is_empty():
525            return None
526
527        # Recommended xen defaults from here:
528        # https://bugzilla.redhat.com/show_bug.cgi?id=1171550#c9
529        # If type block, use name=phy. Otherwise do the same as qemu
530        if self.conn.is_xen() and self.type == self.TYPE_BLOCK:
531            return self.DRIVER_NAME_PHY
532        if self.conn.support.conn_disk_driver_name_qemu():
533            return self.DRIVER_NAME_QEMU
534        return None
535
536    def _get_default_driver_type(self):
537        """
538        Set driver type from passed parameters
539
540        Where possible, we want to force /driver/@type = "raw" if installing
541        a QEMU VM. Without telling QEMU to expect a raw file, the emulator
542        is forced to autodetect, which has security implications:
543
544        https://lists.gnu.org/archive/html/qemu-devel/2008-04/msg00675.html
545        """
546        if self.driver_name != self.DRIVER_NAME_QEMU:
547            return None
548
549        drvtype = self._storage_backend.get_driver_type()
550        return _qemu_sanitize_drvtype(self.type, drvtype)
551
552    def _get_type(self):
553        if self._xmltype:
554            return self._xmltype
555        return self._get_default_type()
556    def _set_type(self, val):
557        self._xmltype = val
558    type = property(_get_type, _set_type)
559
560    def _get_device(self):
561        if self._device:
562            return self._device
563        return self.DEVICE_DISK
564    def _set_device(self, val):
565        self._device = val
566    device = property(_get_device, _set_device)
567
568
569    ############################
570    # Storage backend handling #
571    ############################
572
573    def _change_backend(self, path, vol_object, parent_pool):
574        backend = diskbackend.StorageBackend(self.conn, path,
575                                             vol_object, parent_pool)
576        self._storage_backend = backend
577
578    def set_backend_for_existing_path(self):
579        # This is an entry point for parsexml Disk instances to request
580        # a _storage_backend to be initialized from the XML path. That
581        # will cause validate() to actually validate the path exists.
582        # We need this so addhw XML editing will still validate the disk path
583        if self._storage_backend.is_stub():
584            self._resolve_storage_backend()
585
586    def _resolve_storage_backend(self):
587        """
588        Convert the relevant <source> XML values into self._storage_backend
589        """
590        path = None
591        vol_object = None
592        parent_pool = None
593        self._source_volume_err = None
594        typ = self._get_default_type()
595
596        if self.type == DeviceDisk.TYPE_NETWORK:
597            # Fill in a completed URL for virt-manager UI, path comparison, etc
598            path = self.source.build_url_from_network()
599
600        if typ == DeviceDisk.TYPE_VOLUME:
601            try:
602                parent_pool = self.conn.storagePoolLookupByName(
603                    self.source.pool)
604                vol_object = parent_pool.storageVolLookupByName(
605                    self.source.volume)
606            except Exception as e:
607                self._source_volume_err = str(e)
608                log.debug("Error fetching source pool=%s vol=%s",
609                    self.source.pool, self.source.volume, exc_info=True)
610
611        if vol_object is None and path is None:
612            path = self._get_xmlpath()
613
614        if path and not vol_object and not parent_pool:
615            (vol_object, parent_pool) = diskbackend.manage_path(
616                    self.conn, path)
617
618        self._change_backend(path, vol_object, parent_pool)
619
620    def get_source_path(self):
621        """
622        Source path is a single string representation of the disk source
623        storage. For regular storage this is a FS path. For type=network
624        this is a reconstructed URL. In some cases like rbd:// this may
625        be an entirely synthetic URL format
626        """
627        if (self._storage_backend.is_stub() and not
628            self._storage_backend.get_path()):
629            self._resolve_storage_backend()
630        return self._storage_backend.get_path()
631
632    def set_source_path(self, newpath):
633        if self._storage_backend.will_create_storage():
634            raise xmlutil.DevError(
635                    "Can't change disk path if storage creation info "
636                    "has been set.")
637
638        # User explicitly changed 'path', so try to lookup its storage
639        # object since we may need it
640        (vol_object, parent_pool) = diskbackend.manage_path(self.conn, newpath)
641
642        self._change_backend(newpath, vol_object, parent_pool)
643        self._set_xmlpath(self.get_source_path())
644
645    def set_vol_object(self, vol_object, parent_pool):
646        log.debug("disk.set_vol_object: volxml=\n%s",
647            vol_object.XMLDesc(0))
648        log.debug("disk.set_vol_object: poolxml=\n%s",
649            parent_pool.XMLDesc(0))
650        self._change_backend(None, vol_object, parent_pool)
651        self._set_xmlpath(self.get_source_path())
652
653    def set_vol_install(self, vol_install):
654        log.debug("disk.set_vol_install: name=%s poolxml=\n%s",
655            vol_install.name, vol_install.pool.XMLDesc(0))
656        self._storage_backend = diskbackend.ManagedStorageCreator(
657            self.conn, vol_install)
658        self._set_xmlpath(self.get_source_path())
659
660    def get_vol_object(self):
661        return self._storage_backend.get_vol_object()
662    def get_vol_install(self):
663        return self._storage_backend.get_vol_install()
664    def get_parent_pool(self):
665        return self._storage_backend.get_parent_pool()
666    def get_size(self):
667        return self._storage_backend.get_size()
668
669
670    def _set_source_network_from_storage(self, volxml, poolxml):
671        self.type = "network"
672        if poolxml.auth_type:
673            self.auth_username = poolxml.auth_username
674            self.auth_secret_type = poolxml.auth_type
675            self.auth_secret_uuid = poolxml.auth_secret_uuid
676
677        self.source.set_network_from_storage(volxml, poolxml)
678
679    def _set_network_source_from_backend(self):
680        if (self._storage_backend.get_vol_object() or
681            self._storage_backend.get_vol_install()):
682            volxml = self._storage_backend.get_vol_xml()
683            poolxml = self._storage_backend.get_parent_pool_xml()
684            self._set_source_network_from_storage(volxml, poolxml)
685        elif self._storage_backend.get_path():
686            self.source.set_from_url(self._storage_backend.get_path())
687
688    def _disk_type_to_object_prop_name(self):
689        disk_type = self.type
690        if disk_type == DeviceDisk.TYPE_BLOCK:
691            return "dev"
692        elif disk_type == DeviceDisk.TYPE_DIR:
693            return "dir"
694        elif disk_type == DeviceDisk.TYPE_FILE:
695            return "file"
696        return None
697
698
699    # _xmlpath is an abstraction for source file/block/dir paths, since
700    # they don't have any special properties aside from needing to match
701    # 'type' value with the source property used.
702    def _get_xmlpath(self):
703        if self.source.file:
704            return self.source.file
705        if self.source.dev:
706            return self.source.dev
707        if self.source.dir:
708            return self.source.dir
709        return None
710
711    def _set_xmlpath(self, val):
712        self.source.clear_source()
713
714        if self._storage_backend.get_dev_type() == "network":
715            self._set_network_source_from_backend()
716            return
717
718        propname = self._disk_type_to_object_prop_name()
719        if not propname:
720            return
721        return setattr(self.source, propname, val)
722
723    def set_local_disk_to_clone(self, disk, sparse):
724        """
725        Set a path to manually clone (as in, not through libvirt)
726        """
727        self._storage_backend = diskbackend.CloneStorageCreator(self.conn,
728            self.get_source_path(),
729            disk.get_source_path(),
730            disk.get_size(), sparse)
731
732
733    #####################
734    # Utility functions #
735    #####################
736
737    def is_empty(self):
738        return not bool(self.get_source_path())
739
740    def is_cdrom(self):
741        return self.device == self.DEVICE_CDROM
742    def is_floppy(self):
743        return self.device == self.DEVICE_FLOPPY
744    def is_disk(self):
745        return self.device == self.DEVICE_DISK
746
747    def can_be_empty(self):
748        if self.is_floppy() or self.is_cdrom():
749            return True
750        if self.type in ["file", "block", "dir", "volume", "network"]:
751            return False
752        # Don't error for unknown types
753        return True
754
755    def wants_storage_creation(self):
756        """
757        If true, this disk needs storage creation parameters or things
758        will error.
759        """
760        return not self._storage_backend.exists()
761
762
763    ####################
764    # Storage building #
765    ####################
766
767    def build_storage(self, meter):
768        """
769        Build storage (if required)
770
771        If storage doesn't exist (a non-existent file 'path', or 'vol_install'
772        was specified), we create it.
773        """
774        if not self._storage_backend.will_create_storage():
775            return
776
777        meter = progress.ensure_meter(meter)
778        # pylint: disable=assignment-from-no-return
779        vol_object = self._storage_backend.create(meter)
780        self.storage_was_created = True
781        if not vol_object:
782            return
783
784        parent_pool = self.get_vol_install().pool
785        self._change_backend(None, vol_object, parent_pool)
786
787
788    ######################
789    # validation helpers #
790    ######################
791
792    def validate(self):
793        if self.is_empty():
794            if self._source_volume_err:
795                raise RuntimeError(self._source_volume_err)
796
797            if not self.can_be_empty():
798                raise ValueError(_("Device type '%s' requires a path") %
799                                 self.device)
800
801            return
802
803        if (not self._storage_backend.exists() and
804            not self._storage_backend.will_create_storage()):
805            raise ValueError(
806                _("Must specify storage creation parameters for "
807                  "non-existent path '%s'.") % self.get_source_path())
808
809        self._storage_backend.validate()
810
811    def is_size_conflict(self):
812        """
813        reports if disk size conflicts with available space
814
815        returns a two element tuple:
816            1. first element is True if fatal conflict occurs
817            2. second element is a string description of the conflict or None
818        Non fatal conflicts (sparse disk exceeds available space) will
819        return (False, "description of collision")
820        """
821        return self._storage_backend.is_size_conflict()
822
823    def is_conflict_disk(self):
824        """
825        check if specified storage is in use by any other VMs on passed
826        connection.
827
828        :returns: list of colliding VM names
829        """
830        ret = self.path_in_use_by(self.conn, self.get_source_path(),
831                                  shareable=self.shareable,
832                                  read_only=self.read_only)
833        return ret
834
835
836    ###########################
837    # Misc functional helpers #
838    ###########################
839
840    def sync_path_props(self):
841        """
842        Fills in the values of type, driver_type, and driver_name for
843        the associated backing storage. This needs to be manually called
844        if changing an existing disk's media.
845        """
846        path = self._get_xmlpath()
847
848        self.type = self._get_default_type()
849        self.driver_name = self._get_default_driver_name()
850        self.driver_type = self._get_default_driver_type()
851
852        # Need to retrigger this if self.type changed
853        if path:
854            self._set_xmlpath(path)
855
856    def get_target_prefix(self):
857        """
858        Returns the suggested disk target prefix (hd, xvd, sd ...) for the
859        disk.
860        :returns: str prefix, or None if no reasonable guess can be made
861        """
862        # The upper limits here aren't necessarily 1024, but let the HV
863        # error as appropriate.
864        def _return(prefix):
865            nummap = {
866                "vd": 1024,
867                "xvd": 1024,
868                "fd": 2,
869                "hd": 4,
870                "sd": 1024,
871            }
872            return prefix, nummap[prefix]
873
874        if self.bus == "virtio":
875            return _return("vd")
876        elif self.bus == "xen":
877            return _return("xvd")
878        elif self.bus == "fdc" or self.is_floppy():
879            return _return("fd")
880        elif self.bus == "ide":
881            return _return("hd")
882        # sata, scsi, usb, sd
883        return _return("sd")
884
885    def generate_target(self, skip_targets):
886        """
887        Generate target device ('hda', 'sdb', etc..) for disk, excluding
888        any targets in 'skip_targets'.
889        Sets self.target, and returns the generated value.
890
891        :param skip_targets: list of targets to exclude
892        :returns: generated target
893        """
894        prefix, maxnode = self.get_target_prefix()
895        skip_targets = [t for t in skip_targets if t and t.startswith(prefix)]
896        skip_targets.sort()
897
898        def get_target():
899            first_found = None
900
901            for i in range(maxnode):
902                gen_t = prefix + self.num_to_target(i + 1)
903                if gen_t in skip_targets:
904                    skip_targets.remove(gen_t)
905                    continue
906                if not skip_targets:
907                    return gen_t
908                elif not first_found:
909                    first_found = gen_t
910            if first_found:
911                return first_found
912
913        ret = get_target()
914        if ret:
915            self.target = ret
916            return ret
917
918        raise ValueError(
919            ngettext("Only %(number)s disk for bus '%(bus)s' are supported",
920                     "Only %(number)s disks for bus '%(bus)s' are supported",
921                     maxnode) %
922            {"number": maxnode, "bus": self.bus})
923
924    def change_bus(self, guest, newbus):
925        """
926        Change the bus value for an existing disk, which has some
927        follow on side effects.
928        """
929        if self.bus == newbus:
930            return
931
932        oldprefix = self.get_target_prefix()[0]
933        self.bus = newbus
934
935        self.address.clear()
936
937        if oldprefix == self.get_target_prefix()[0]:
938            return
939
940        used = [disk.target for disk in guest.devices.disk]
941
942        if self.target:
943            used.remove(self.target)
944
945        self.target = None
946        self.generate_target(used)
947
948
949    #########################
950    # set_defaults handling #
951    #########################
952
953    def _default_bus(self, guest):
954        if self.is_floppy():
955            return "fdc"
956        if guest.os.is_xenpv():
957            return "xen"
958        if not guest.os.is_hvm():
959            # This likely isn't correct, but it's kind of a catch all
960            # for virt types we don't know how to handle.
961            return "ide"
962        if self.is_disk() and guest.supports_virtiodisk():
963            return "virtio"
964        if (self.is_cdrom() and
965            guest.supports_virtioscsi() and
966            not guest.os.is_x86()):
967            # x86 long time default has been IDE CDROM, stick with that to
968            # avoid churn, but every newer virt arch that supports virtio-scsi
969            # should use it
970            return "scsi"
971        if guest.os.is_arm():
972            return "sd"
973        if guest.os.is_q35():
974            return "sata"
975        return "ide"
976
977    def set_defaults(self, guest):
978        if not self._device:
979            self._device = self._get_device()
980        if not self._xmltype:
981            self._xmltype = self._get_default_type()
982        if not self.driver_name:
983            self.driver_name = self._get_default_driver_name()
984        if not self.driver_type:
985            self.driver_type = self._get_default_driver_type()
986        if not self.bus:
987            self.bus = self._default_bus(guest)
988        if self.is_cdrom():
989            self.read_only = True
990
991        if (self.conn.is_qemu() and
992            self.is_disk() and
993            self.type == self.TYPE_BLOCK):
994            if not self.driver_cache:
995                self.driver_cache = self.CACHE_MODE_NONE
996            if not self.driver_io:
997                self.driver_io = self.IO_MODE_NATIVE
998
999        if not self.target:
1000            used_targets = [d.target for d in guest.devices.disk if d.target]
1001            self.generate_target(used_targets)
1002