"""Fake libvirt hosts, domains and node devices that tests of several modules share: the migration source and destination, the host devices of a q35 host, and the IOMMU groups of the PCI passthrough rules.""" from unittest.mock import MagicMock import libvirt from lxml import etree from vrtManager.instance import wvmInstances def no_domain(): error = libvirt.libvirtError("Domain not found") error.err = (libvirt.VIR_ERR_NO_DOMAIN,) return error class FakeDomain: def __init__(self, xml="", snapshots=0, saved=False, undefine_fails=False, undefine_lost=False, started=False): self.calls = [] self.xml = xml self.snapshots = snapshots self.saved = saved self.undefine_fails = undefine_fails self.undefine_lost = undefine_lost # undefined, but the reply is lost self.started = started # started by another client during the move self.undefined = None self.redefined = [] self.restored_autostart = None def migrate(self, dconn, flags, dname, uri, bandwidth): self.calls.append((flags, uri)) def name(self): return "vm" def UUIDString(self): return "u-1" inactive_xml = None # the persistent definition, when it differs def XMLDesc(self, flags): if flags & libvirt.VIR_DOMAIN_XML_INACTIVE and self.inactive_xml: return self.inactive_xml return self.xml def snapshotNum(self, flags=0): return self.snapshots def hasManagedSaveImage(self, flags=0): return self.saved def undefineFlags(self, flags): if self.undefine_fails: raise libvirt.libvirtError("undefine failed") self.undefined = flags if self.undefine_lost: raise libvirt.libvirtError("Cannot recv data") def isPersistent(self): return self.undefined is None or bool(self.redefined) def isActive(self): if not self.started and self.undefined is not None: raise no_domain() # libvirt: an undefined shut-off VM is gone return self.started def autostart(self): return 1 def setAutostart(self, value): self.restored_autostart = value def connect(self): return self def defineXML(self, xml): self.redefined.append(xml) return self def no_volume(): error = libvirt.libvirtError("Storage volume not found") error.err = (libvirt.VIR_ERR_NO_STORAGE_VOL,) return error class FakeVolume: def __init__(self, backing): self.backing = backing def XMLDesc(self, flags=0): if self.backing is None: return "x" return f"{self.backing}" class FakePool: def __init__(self, host, active=True): self.host = host self.active = active def isActive(self): return self.active def XMLDesc(self, flags=0): return "/elsewhere" def refresh(self, flags=0): self.host.refreshes += 1 self.host.volumes |= self.host.unlisted self.host.unlisted = set() def storageVolLookupByName(self, name): return self.host.storageVolLookupByPath(name) class FakeHost: """The destination's libvirt connection. volumes: the paths its pools list; unlisted: files its pools show after a refresh.""" def __init__(self, existing=(), volumes=(), unlisted=(), backing=None, inactive_pools=()): self.existing = set(existing) self.defined = [] self.new = [] self.volumes = set(volumes) self.unlisted = set(unlisted) self.backing = backing or {} # path: the path of its backing file self.inactive_pools = set(inactive_pools) self.refreshes = 0 def storageVolLookupByPath(self, path): if path not in self.volumes: raise no_volume() return FakeVolume(self.backing.get(path)) def storagePoolLookupByName(self, name): return FakePool(self, active=name not in self.inactive_pools) def listAllStoragePools(self, flags=0): return [FakePool(self)] def lookup(self, key): if key in self.existing: return FakeDomain() raise no_domain() lookupByName = lookupByUUIDString = lookup def defineXML(self, xml): self.defined.append(xml) self.new.append(FakeDomain()) return self.new[-1] class FakeSource: def __init__(self, state, nvram=None, **domain): self.state = state self.nvram = nvram self.instance = FakeDomain(**domain) def get_nvram(self): return self.nvram reads = 0 def get_status(self): self.reads += 1 return self.state def get_arch(self): return "x86_64" def get_dom_emulator(self): return "/usr/bin/qemu-system-x86_64" def destination(host=None): dest = wvmInstances.__new__(wvmInstances) dest.wvm = host or FakeHost() dest.get_emulator = lambda arch: "/usr/bin/qemu-system-x86_64" return dest def with_disks(*disks): return "%s" % "".join(disks) def pci(slot, product, code, driver=None, group=None): iommu = f"
" \ if group is not None else "" driver = f"{driver}" if driver else "" return (f"pci_0000_00_{int(slot, 16):02x}_0", f"""pci_0000_00_{int(slot, 16):02x}_0computer{driver} {code}00{int(slot, 16)}0 {product}Red Hat, Inc.{iommu}""") def child(name, parent, capability): return name, f"{name}{parent}{capability}" def usb(name, parent, bus, device, product="QEMU Tablet", vendor_id="0x0627", product_id="0x0001"): return name, f"""{name}{parent}{bus} {device}{product}Adomax """ def interface(name, parent, code): return child(name, parent, f"0{code}") NODES = [ pci("0x06", "Virtio network device", "0x020000", "virtio-pci", group=3), child("net_eth0", "pci_0000_00_06_0", "eth0"), pci("0x03", "Virtio block device", "0x010000", "virtio-pci", group=2), child("virtio1", "pci_0000_00_03_0", ""), # a level between, not a resource child("block_vda", "virtio1", "/dev/vda"), pci("0x02", "Virtio 1.0 GPU", "0x030000", "virtio-pci", group=2), child("drm_card0", "pci_0000_00_02_0", "/dev/dri/card0primary"), child("drm_render", "pci_0000_00_02_0", "/dev/dri/renderD128render"), pci("0x0a", "Ethernet controller", "0x020000", "vfio-pci", group=4), pci("0x01", "PIIX3 USB", "0x0c0300", "uhci_hcd"), usb("usb_usb1", "pci_0000_00_01_0", 1, 1, "1.1 root hub", "0x1d6b", "0x0001"), interface("usb_1_0_1_0", "usb_usb1", 9), usb("usb_1_1", "usb_usb1", 1, 2), interface("usb_1_1_1_0", "usb_1_1", 3), usb("usb_1_2", "usb_usb1", 1, 3), interface("usb_1_2_1_0", "usb_1_2", 3), ] def node(name, xml): return MagicMock(**{"name.return_value": name, "XMLDesc.return_value": xml}) BY_MODEL = "{address}" def by_model(address="", auto=""): return BY_MODEL.format(address=address, auto=auto) def domain(*devices): return f"kiosk-1{''.join(devices)}" def device(slot, product, code, driver, group, members=None, bridge=False): """A PCI node of an IOMMU test host: a device on bus 0 at slot, in IOMMU group with the slots members (its own by default).""" members = members or [slot] addresses = "".join(f"
" for m in members) driver_xml = f"{driver}" if driver else "" bridge_xml = "" if bridge else "" xml = (f"pci_0000_00_{slot:02x}_0computer{driver_xml}" f"{code}00{slot}0" f"{product}Intel{bridge_xml}" f"{addresses}") return f"pci_0000_00_{slot:02x}_0", etree.fromstring(xml) IOMMU_NODES = dict([ device(0x02, "PCIe Root port", "0x060400", "pcieport", 2, bridge=True), device(0x03, "SATA controller", "0x010601", "ahci", 7, [0x03, 0x04, 0x05]), device(0x04, "SMBus controller", "0x0c0500", "i801_smbus", 7, [0x03, 0x04, 0x05]), device(0x05, "LPC controller", "0x060100", "lpc_ich", 7, [0x03, 0x04, 0x05]), # class 0x06, not a bridge device(0x06, "Gigabit NIC", "0x020000", "e1000e", 12), device(0x07, "Spare NIC", "0x020000", "vfio-pci", 13), device(0x08, "NIC port 0", "0x020000", "vfio-pci", 14, [0x08, 0x09, 0x0a]), device(0x09, "NIC port 1", "0x020000", None, 14, [0x08, 0x09, 0x0a]), device(0x0a, "Downstream port", "0x060400", "pcieport", 14, [0x08, 0x09, 0x0a], bridge=True), device(0x0b, "Accelerator", "0x120000", "vfio-pci", 15, [0x0b, 0x0c]), # 0x0c cannot be read device(0x0d, "GPU", "0x030000", "nouveau", 16, [0x0d, 0x0e]), device(0x0e, "GPU audio", "0x040300", "snd_hda_intel", 16, [0x0d, 0x0e]), ]) # what the host has below: a disk on the SATA controller, its interface on the e1000e IOMMU_NODES["block_sda"] = etree.fromstring("block_sdapci_0000_00_03_0" "/dev/sda") IOMMU_NODES["net_eth1"] = etree.fromstring("net_eth1pci_0000_00_06_0" "eth1") def vm(name, hostdevs, saved=None): """A VM with hostdevs as it runs and, given saved, persistent with those for its next start.""" def describe(flags=0): return f"{saved if flags else hostdevs}" return MagicMock(**{"name.return_value": name, "XMLDesc.side_effect": describe, "isPersistent.return_value": saved is not None}) PCI_0A = "
" USB_1_3 = "
" DUID = "00:01:00:01:2e:8b:3c:41:52:54:00:00:00:40" def lease(ip, mac=None, expiry=1793265489, hostname=None, clientid=None, family=0): return {"iface": "virbr9", "expirytime": expiry, "type": family, "mac": mac, "ipaddr": ip, "prefix": 24, "hostname": hostname, "clientid": clientid, "iaid": None} # a host with a NAT network that hands out addresses (libvirt's test driver node file) NAT_NETWORK_NODE = """ lab lan direct web1024hvm """ CEPH = "8f2c0000-0000-4000-8000-000000000001"