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