# Copyright 2010 OpenStack Foundation # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import collections import os import sys import textwrap import time import typing as ty import fixtures from lxml import etree import mock from oslo_log import log as logging from oslo_utils.fixture import uuidsentinel as uuids from oslo_utils import versionutils from nova import conf from nova.objects import fields as obj_fields from nova.tests.unit.virt.libvirt import fake_libvirt_data from nova.virt.libvirt import config as vconfig from nova.virt.libvirt import driver as libvirt_driver # Allow passing None to the various connect methods # (i.e. allow the client to rely on default URLs) allow_default_uri_connection = True # Has libvirt connection been used at least once connection_used = False def _reset(): global allow_default_uri_connection allow_default_uri_connection = True LOG = logging.getLogger(__name__) CONF = conf.CONF # virDomainState VIR_DOMAIN_NOSTATE = 0 VIR_DOMAIN_RUNNING = 1 VIR_DOMAIN_BLOCKED = 2 VIR_DOMAIN_PAUSED = 3 VIR_DOMAIN_SHUTDOWN = 4 VIR_DOMAIN_SHUTOFF = 5 VIR_DOMAIN_CRASHED = 6 # NOTE(mriedem): These values come from include/libvirt/libvirt-domain.h VIR_DOMAIN_XML_SECURE = 1 VIR_DOMAIN_XML_INACTIVE = 2 VIR_DOMAIN_XML_UPDATE_CPU = 4 VIR_DOMAIN_XML_MIGRATABLE = 8 VIR_DOMAIN_BLOCK_COPY_SHALLOW = 1 VIR_DOMAIN_BLOCK_COPY_REUSE_EXT = 2 VIR_DOMAIN_BLOCK_COPY_TRANSIENT_JOB = 4 VIR_DOMAIN_BLOCK_REBASE_SHALLOW = 1 VIR_DOMAIN_BLOCK_REBASE_REUSE_EXT = 2 VIR_DOMAIN_BLOCK_REBASE_COPY = 8 VIR_DOMAIN_BLOCK_REBASE_RELATIVE = 16 VIR_DOMAIN_BLOCK_REBASE_COPY_DEV = 32 # virDomainBlockResize VIR_DOMAIN_BLOCK_RESIZE_BYTES = 1 VIR_DOMAIN_BLOCK_JOB_ABORT_ASYNC = 1 VIR_DOMAIN_BLOCK_JOB_ABORT_PIVOT = 2 VIR_DOMAIN_EVENT_ID_LIFECYCLE = 0 VIR_DOMAIN_EVENT_DEFINED = 0 VIR_DOMAIN_EVENT_UNDEFINED = 1 VIR_DOMAIN_EVENT_STARTED = 2 VIR_DOMAIN_EVENT_SUSPENDED = 3 VIR_DOMAIN_EVENT_RESUMED = 4 VIR_DOMAIN_EVENT_STOPPED = 5 VIR_DOMAIN_EVENT_SHUTDOWN = 6 VIR_DOMAIN_EVENT_PMSUSPENDED = 7 VIR_DOMAIN_EVENT_ID_DEVICE_REMOVED = 15 VIR_DOMAIN_EVENT_ID_DEVICE_REMOVAL_FAILED = 22 VIR_DOMAIN_EVENT_SUSPENDED_MIGRATED = 1 VIR_DOMAIN_EVENT_SUSPENDED_POSTCOPY = 7 VIR_DOMAIN_UNDEFINE_MANAGED_SAVE = 1 VIR_DOMAIN_UNDEFINE_NVRAM = 4 VIR_DOMAIN_AFFECT_CURRENT = 0 VIR_DOMAIN_AFFECT_LIVE = 1 VIR_DOMAIN_AFFECT_CONFIG = 2 VIR_CPU_COMPARE_ERROR = -1 VIR_CPU_COMPARE_INCOMPATIBLE = 0 VIR_CPU_COMPARE_IDENTICAL = 1 VIR_CPU_COMPARE_SUPERSET = 2 VIR_CRED_USERNAME = 1 VIR_CRED_AUTHNAME = 2 VIR_CRED_LANGUAGE = 3 VIR_CRED_CNONCE = 4 VIR_CRED_PASSPHRASE = 5 VIR_CRED_ECHOPROMPT = 6 VIR_CRED_NOECHOPROMPT = 7 VIR_CRED_REALM = 8 VIR_CRED_EXTERNAL = 9 VIR_MIGRATE_LIVE = 1 VIR_MIGRATE_PEER2PEER = 2 VIR_MIGRATE_TUNNELLED = 4 VIR_MIGRATE_PERSIST_DEST = 8 VIR_MIGRATE_UNDEFINE_SOURCE = 16 VIR_MIGRATE_NON_SHARED_INC = 128 VIR_MIGRATE_AUTO_CONVERGE = 8192 VIR_MIGRATE_POSTCOPY = 32768 VIR_MIGRATE_TLS = 65536 VIR_NODE_CPU_STATS_ALL_CPUS = -1 VIR_DOMAIN_START_PAUSED = 1 # libvirtError enums # (Intentionally different from what's in libvirt. We do this to check, # that consumers of the library are using the symbolic names rather than # hardcoding the numerical values) VIR_FROM_QEMU = 100 VIR_FROM_DOMAIN = 200 VIR_FROM_SECRET = 300 VIR_FROM_NWFILTER = 330 VIR_FROM_REMOTE = 340 VIR_FROM_RPC = 345 VIR_FROM_NODEDEV = 666 VIR_ERR_INVALID_ARG = 8 VIR_ERR_NO_SUPPORT = 3 VIR_ERR_XML_ERROR = 27 VIR_ERR_XML_DETAIL = 350 VIR_ERR_NO_DOMAIN = 420 VIR_ERR_OPERATION_FAILED = 510 VIR_ERR_OPERATION_INVALID = 55 VIR_ERR_OPERATION_TIMEOUT = 68 VIR_ERR_NO_NWFILTER = 620 VIR_ERR_SYSTEM_ERROR = 900 VIR_ERR_INTERNAL_ERROR = 950 VIR_ERR_CONFIG_UNSUPPORTED = 951 VIR_ERR_NO_NODE_DEVICE = 667 VIR_ERR_INVALID_SECRET = 65 VIR_ERR_NO_SECRET = 66 VIR_ERR_AGENT_UNRESPONSIVE = 86 VIR_ERR_ARGUMENT_UNSUPPORTED = 74 VIR_ERR_OPERATION_UNSUPPORTED = 84 VIR_ERR_DEVICE_MISSING = 99 # Readonly VIR_CONNECT_RO = 1 # virConnectBaselineCPU flags VIR_CONNECT_BASELINE_CPU_EXPAND_FEATURES = 1 # snapshotCreateXML flags VIR_DOMAIN_SNAPSHOT_CREATE_NO_METADATA = 4 VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY = 16 VIR_DOMAIN_SNAPSHOT_CREATE_REUSE_EXT = 32 VIR_DOMAIN_SNAPSHOT_CREATE_QUIESCE = 64 # blockCommit flags VIR_DOMAIN_BLOCK_COMMIT_RELATIVE = 4 VIR_CONNECT_LIST_DOMAINS_ACTIVE = 1 VIR_CONNECT_LIST_DOMAINS_INACTIVE = 2 # virConnectListAllNodeDevices flags VIR_CONNECT_LIST_NODE_DEVICES_CAP_PCI_DEV = 2 VIR_CONNECT_LIST_NODE_DEVICES_CAP_NET = 1 << 4 VIR_CONNECT_LIST_NODE_DEVICES_CAP_VDPA = 1 << 17 # secret type VIR_SECRET_USAGE_TYPE_NONE = 0 VIR_SECRET_USAGE_TYPE_VOLUME = 1 VIR_SECRET_USAGE_TYPE_CEPH = 2 VIR_SECRET_USAGE_TYPE_ISCSI = 3 # metadata types VIR_DOMAIN_METADATA_DESCRIPTION = 0 VIR_DOMAIN_METADATA_TITLE = 1 VIR_DOMAIN_METADATA_ELEMENT = 2 # Libvirt version to match MIN_LIBVIRT_VERSION in driver.py FAKE_LIBVIRT_VERSION = versionutils.convert_version_to_int( libvirt_driver.MIN_LIBVIRT_VERSION) # Libvirt version to match MIN_QEMU_VERSION in driver.py FAKE_QEMU_VERSION = versionutils.convert_version_to_int( libvirt_driver.MIN_QEMU_VERSION) PCI_VEND_ID = '8086' PCI_VEND_NAME = 'Intel Corporation' PCI_PROD_ID = '1533' PCI_PROD_NAME = 'I210 Gigabit Network Connection' PCI_DRIVER_NAME = 'igb' PF_PROD_ID = '1528' PF_PROD_NAME = 'Ethernet Controller 10-Gigabit X540-AT2' PF_DRIVER_NAME = 'ixgbe' PF_CAP_TYPE = 'virt_functions' VF_PROD_ID = '1515' VF_PROD_NAME = 'X540 Ethernet Controller Virtual Function' VF_DRIVER_NAME = 'ixgbevf' VF_CAP_TYPE = 'phys_function' MDEV_CAPABLE_VEND_ID = '10DE' MDEV_CAPABLE_VEND_NAME = 'Nvidia' MDEV_CAPABLE_PROD_ID = '0FFE' MDEV_CAPABLE_PROD_NAME = 'GRID M60-0B' MDEV_CAPABLE_DRIVER_NAME = 'nvidia' MDEV_CAPABLE_CAP_TYPE = 'mdev_types' NVIDIA_11_VGPU_TYPE = 'nvidia-11' NVIDIA_12_VGPU_TYPE = 'nvidia-12' PGPU1_PCI_ADDR = 'pci_0000_81_00_0' PGPU2_PCI_ADDR = 'pci_0000_81_01_0' PGPU3_PCI_ADDR = 'pci_0000_81_02_0' os_uname = collections.namedtuple( 'uname_result', ['sysname', 'nodename', 'release', 'version', 'machine'], ) def _get_libvirt_nodedev_name(bus, slot, function): """Convert an address to a libvirt device name string.""" return f'pci_0000_{bus:02x}_{slot:02x}_{function:d}' class FakePCIDevice(object): """Generate a fake PCI device. Generate a fake PCI devices corresponding to one of the following real-world PCI devices. - I210 Gigabit Network Connection (8086:1533) - Ethernet Controller 10-Gigabit X540-AT2 (8086:1528) - X540 Ethernet Controller Virtual Function (8086:1515) """ pci_default_parent = "pci_0000_80_01_0" pci_device_template = textwrap.dedent(""" pci_0000_%(bus)02x_%(slot)02x_%(function)d /sys/devices/pci0000:80/0000:80:01.0/0000:%(bus)02x:%(slot)02x.%(function)d %(parent)s %(driver)s 0 %(bus)d %(slot)d %(function)d %(prod_name)s %(vend_name)s %(capability)s
""".strip()) # noqa cap_templ = "%(addresses)s" addr_templ = "
" # noqa mdevtypes_templ = textwrap.dedent(""" GRID M60-0Bvfio-pci %(instances)s """.strip()) # noqa is_capable_of_mdevs = False def __init__( self, dev_type, bus, slot, function, iommu_group, numa_node, *, vf_ratio=None, multiple_gpu_types=False, parent=None, vend_id=None, vend_name=None, prod_id=None, prod_name=None, driver_name=None, ): """Populate pci devices :param dev_type: (str) Indicates the type of the device (PCI, PF, VF, MDEV_TYPES). :param bus: (int) Bus number of the device. :param slot: (int) Slot number of the device. :param function: (int) Function number of the device. :param iommu_group: (int) IOMMU group ID. :param numa_node: (int) NUMA node of the device. :param vf_ratio: (int) Ratio of Virtual Functions on Physical. Only applicable if ``dev_type`` is one of: ``PF``, ``VF``. :param multiple_gpu_types: (bool) Supports different vGPU types. :param parent: (int, int, int) A tuple of bus, slot and function corresponding to the parent. :param vend_id: (str) The vendor ID. :param vend_name: (str) The vendor name. :param prod_id: (str) The product ID. :param prod_name: (str) The product name. :param driver_name: (str) The driver name. """ self.dev_type = dev_type self.bus = bus self.slot = slot self.function = function self.iommu_group = iommu_group self.numa_node = numa_node self.vf_ratio = vf_ratio self.multiple_gpu_types = multiple_gpu_types self.parent = parent self.vend_id = vend_id self.vend_name = vend_name self.prod_id = prod_id self.prod_name = prod_name self.driver_name = driver_name self.generate_xml() def generate_xml(self, skip_capability=False): # initial validation assert self.dev_type in ('PCI', 'VF', 'PF', 'MDEV_TYPES'), ( f'got invalid dev_type {self.dev_type}') if self.dev_type == 'PCI': assert not self.vf_ratio, 'vf_ratio does not apply for PCI devices' if self.dev_type in ('PF', 'VF'): assert self.vf_ratio, 'require vf_ratio for PFs and VFs' if self.dev_type == 'VF': assert self.parent, 'require parent for VFs' assert isinstance(self.parent, tuple), 'parent must be an address' assert len(self.parent) == 3, 'parent must be an address' vend_id = self.vend_id or PCI_VEND_ID vend_name = self.vend_name or PCI_VEND_NAME capability = '' if self.dev_type == 'PCI': prod_id = self.prod_id or PCI_PROD_ID prod_name = self.prod_name or PCI_PROD_NAME driver = self.driver_name or PCI_DRIVER_NAME elif self.dev_type == 'PF': prod_id = self.prod_id or PF_PROD_ID prod_name = self.prod_name or PF_PROD_NAME driver = self.driver_name or PF_DRIVER_NAME if not skip_capability: capability = self.cap_templ % { 'cap_type': PF_CAP_TYPE, 'addresses': '\n'.join([ self.addr_templ % { 'bus': self.bus, # these are the slot, function values of the child # VFs, we can only assign 8 functions to a slot # (0-7) so bump the slot each time we exceed this 'slot': self.slot + (x // 8), # ...and wrap the function value 'function': x % 8, # the offset is because the PF is occupying function 0 } for x in range(1, self.vf_ratio + 1)]) } elif self.dev_type == 'VF': prod_id = self.prod_id or VF_PROD_ID prod_name = self.prod_name or VF_PROD_NAME driver = self.driver_name or VF_DRIVER_NAME if not skip_capability: capability = self.cap_templ % { 'cap_type': VF_CAP_TYPE, 'addresses': self.addr_templ % { 'bus': self.bus, # this is the slot, function value of the parent PF # if we're e.g. device 8, we'll have a different slot # to our parent so reverse this 'slot': self.slot - ((self.vf_ratio + 1) // 8), # the parent PF is always function 0 'function': 0, } } elif self.dev_type == 'MDEV_TYPES': prod_id = self.prod_id or MDEV_CAPABLE_PROD_ID prod_name = self.prod_name or MDEV_CAPABLE_PROD_NAME driver = self.driver_name or MDEV_CAPABLE_DRIVER_NAME vend_id = self.vend_id or MDEV_CAPABLE_VEND_ID vend_name = self.vend_name or MDEV_CAPABLE_VEND_NAME types = [self.mdevtypes_templ % { 'type_id': NVIDIA_11_VGPU_TYPE, 'instances': 16, }] if self.multiple_gpu_types: types.append(self.mdevtypes_templ % { 'type_id': NVIDIA_12_VGPU_TYPE, 'instances': 8, }) if not skip_capability: capability = self.cap_templ % { 'cap_type': MDEV_CAPABLE_CAP_TYPE, 'addresses': '\n'.join(types) } self.is_capable_of_mdevs = True parent = self.pci_default_parent if self.parent: parent = _get_libvirt_nodedev_name(*self.parent) self.pci_device = self.pci_device_template % { 'bus': self.bus, 'slot': self.slot, 'function': self.function, 'vend_id': vend_id, 'vend_name': vend_name, 'prod_id': prod_id, 'prod_name': prod_name, 'driver': driver, 'capability': capability, 'iommu_group': self.iommu_group, 'numa_node': self.numa_node, 'parent': parent, } # -1 is the sentinel set in /sys/bus/pci/devices/*/numa_node # for no NUMA affinity. When the numa_node is set to -1 on a device # Libvirt omits the NUMA element so we remove it. if self.numa_node == -1: self.pci_device = self.pci_device.replace("", "") def XMLDesc(self, flags): return self.pci_device # TODO(stephenfin): Remove all of these HostFooDevicesInfo objects in favour of # a unified devices object class HostPCIDevicesInfo(object): """Represent a pool of host PCI devices.""" TOTAL_NUMA_NODES = 2 def __init__(self, num_pci=0, num_pfs=2, num_vfs=8, num_mdevcap=0, numa_node=None, multiple_gpu_types=False): """Create a new HostPCIDevicesInfo object. :param num_pci: (int) The number of (non-SR-IOV) and (non-MDEV capable) PCI devices. :param num_pfs: (int) The number of PCI SR-IOV Physical Functions. :param num_vfs: (int) The number of PCI SR-IOV Virtual Functions. :param num_mdevcap: (int) The number of PCI devices capable of creating mediated devices. :param numa_node: (int) NUMA node of the device; if set all of the devices will be assigned to the specified node else they will be split between ``$TOTAL_NUMA_NODES`` nodes. :param multiple_gpu_types: (bool) Supports different vGPU types """ self.devices = {} if not (num_vfs or num_pfs) and not num_mdevcap: return if num_vfs and not num_pfs: raise ValueError('Cannot create VFs without PFs') if num_pfs and num_vfs % num_pfs: raise ValueError('num_vfs must be a factor of num_pfs') bus = 0x81 slot = 0x0 function = 0 iommu_group = 40 # totally arbitrary number # Generate PCI devs for dev in range(num_pci): self.add_device( dev_type='PCI', bus=bus, slot=slot, function=function, iommu_group=iommu_group, numa_node=self._calc_numa_node(dev, numa_node)) slot += 1 iommu_group += 1 # Generate MDEV capable devs for dev in range(num_mdevcap): self.add_device( dev_type='MDEV_TYPES', bus=bus, slot=slot, function=function, iommu_group=iommu_group, numa_node=self._calc_numa_node(dev, numa_node), multiple_gpu_types=multiple_gpu_types) slot += 1 iommu_group += 1 vf_ratio = num_vfs // num_pfs if num_pfs else 0 # Generate PFs for dev in range(num_pfs): function = 0 numa_node_pf = self._calc_numa_node(dev, numa_node) self.add_device( dev_type='PF', bus=bus, slot=slot, function=function, iommu_group=iommu_group, numa_node=numa_node_pf, vf_ratio=vf_ratio) parent = (bus, slot, function) # Generate VFs for _ in range(vf_ratio): function += 1 iommu_group += 1 if function % 8 == 0: # functions must be 0-7 slot += 1 function = 0 self.add_device( dev_type='VF', bus=bus, slot=slot, function=function, iommu_group=iommu_group, numa_node=numa_node_pf, vf_ratio=vf_ratio, parent=parent) slot += 1 def add_device( self, dev_type, bus, slot, function, iommu_group, numa_node, vf_ratio=None, multiple_gpu_types=False, parent=None, vend_id=None, vend_name=None, prod_id=None, prod_name=None, driver_name=None, ): pci_dev_name = _get_libvirt_nodedev_name(bus, slot, function) LOG.info('Generating %s device %r', dev_type, pci_dev_name) dev = FakePCIDevice( dev_type=dev_type, bus=bus, slot=slot, function=function, iommu_group=iommu_group, numa_node=numa_node, vf_ratio=vf_ratio, multiple_gpu_types=multiple_gpu_types, parent=parent, vend_id=vend_id, vend_name=vend_name, prod_id=prod_id, prod_name=prod_name, driver_name=driver_name) self.devices[pci_dev_name] = dev return dev @classmethod def _calc_numa_node(cls, dev, numa_node): return dev % cls.TOTAL_NUMA_NODES if numa_node is None else numa_node def get_all_devices(self): return self.devices.keys() def get_device_by_name(self, device_name): pci_dev = self.devices.get(device_name) return pci_dev def get_all_mdev_capable_devices(self): return [dev for dev in self.devices if self.devices[dev].is_capable_of_mdevs] class FakeMdevDevice(object): template = """ %(dev_name)s /sys/devices/pci0000:00/0000:00:02.0/%(path)s %(parent)s vfio_mdev """ def __init__(self, dev_name, type_id, parent): self.xml = self.template % { 'dev_name': dev_name, 'type_id': type_id, 'path': dev_name[len('mdev_'):], 'parent': parent} def XMLDesc(self, flags): return self.xml class HostMdevDevicesInfo(object): def __init__(self, devices=None): if devices is not None: self.devices = devices else: self.devices = {} def get_all_devices(self): return self.devices.keys() def get_device_by_name(self, device_name): dev = self.devices[device_name] return dev class FakeVDPADevice: template = textwrap.dedent(""" %(name)s %(path)s %(parent)s vhost_vdpa /dev/vhost-vdpa-%(idx)d """.strip()) def __init__(self, name, idx, parent): assert isinstance(parent, FakePCIDevice) assert parent.dev_type == 'VF' self.name = name self.idx = idx self.parent = parent self.generate_xml() def generate_xml(self): pf_pci = self.parent.parent vf_pci = (self.parent.bus, self.parent.slot, self.parent.function) pf_addr = '0000:%02x:%02x.%d' % pf_pci vf_addr = '0000:%02x:%02x.%d' % vf_pci parent = _get_libvirt_nodedev_name(*vf_pci) path = f'/sys/devices/pci0000:00/{pf_addr}/{vf_addr}/vdpa{self.idx}' self.xml = self.template % { 'name': self.name, 'idx': self.idx, 'path': path, 'parent': parent, } def XMLDesc(self, flags): return self.xml class HostVDPADevicesInfo: def __init__(self): self.devices = {} def get_all_devices(self): return self.devices.keys() def get_device_by_name(self, device_name): dev = self.devices[device_name] return dev def add_device(self, name, idx, parent): LOG.info('Generating vDPA device %r', name) dev = FakeVDPADevice(name=name, idx=idx, parent=parent) self.devices[name] = dev return dev class HostInfo(object): def __init__(self, cpu_nodes=1, cpu_sockets=1, cpu_cores=2, cpu_threads=1, kB_mem=16780000, mempages=None): """Create a new Host Info object :param cpu_nodes: (int) the number of NUMA cell, 1 for unusual NUMA topologies or uniform :param cpu_sockets: (int) number of CPU sockets per node if nodes > 1, total number of CPU sockets otherwise :param cpu_cores: (int) number of cores per socket :param cpu_threads: (int) number of threads per core :param kB_mem: (int) memory size in KBytes """ self.arch = obj_fields.Architecture.X86_64 self.kB_mem = kB_mem self.cpus = cpu_nodes * cpu_sockets * cpu_cores * cpu_threads self.cpu_mhz = 800 self.cpu_nodes = cpu_nodes self.cpu_cores = cpu_cores self.cpu_threads = cpu_threads self.cpu_sockets = cpu_sockets self.cpu_model = "Penryn" self.cpu_vendor = "Intel" self.numa_topology = NUMATopology(self.cpu_nodes, self.cpu_sockets, self.cpu_cores, self.cpu_threads, self.kB_mem, mempages) class NUMATopology(vconfig.LibvirtConfigCapsNUMATopology): """A batteries-included variant of LibvirtConfigCapsNUMATopology. Provides sane defaults for LibvirtConfigCapsNUMATopology that can be used in tests as is, or overridden where necessary. """ def __init__(self, cpu_nodes=4, cpu_sockets=1, cpu_cores=1, cpu_threads=2, kb_mem=1048576, mempages=None, **kwargs): super(NUMATopology, self).__init__(**kwargs) cpu_count = 0 cell_count = 0 for socket_count in range(cpu_sockets): for cell_num in range(cpu_nodes): cell = vconfig.LibvirtConfigCapsNUMACell() cell.id = cell_count cell.memory = kb_mem // (cpu_nodes * cpu_sockets) for cpu_num in range(cpu_cores * cpu_threads): cpu = vconfig.LibvirtConfigCapsNUMACPU() cpu.id = cpu_count cpu.socket_id = socket_count cpu.core_id = cpu_num // cpu_threads cpu.siblings = set([cpu_threads * (cpu_count // cpu_threads) + thread for thread in range(cpu_threads)]) cell.cpus.append(cpu) cpu_count += 1 # If no mempages are provided, use only the default 4K pages if mempages: cell.mempages = mempages[cell_count] else: cell.mempages = create_mempages([(4, cell.memory // 4)]) self.cells.append(cell) cell_count += 1 def create_mempages(mappings): """Generate a list of LibvirtConfigCapsNUMAPages objects. :param mappings: (dict) A mapping of page size to quantity of said pages. :returns: [LibvirtConfigCapsNUMAPages, ...] """ mempages = [] for page_size, page_qty in mappings: mempage = vconfig.LibvirtConfigCapsNUMAPages() mempage.size = page_size mempage.total = page_qty mempages.append(mempage) return mempages VIR_DOMAIN_JOB_NONE = 0 VIR_DOMAIN_JOB_BOUNDED = 1 VIR_DOMAIN_JOB_UNBOUNDED = 2 VIR_DOMAIN_JOB_COMPLETED = 3 VIR_DOMAIN_JOB_FAILED = 4 VIR_DOMAIN_JOB_CANCELLED = 5 def _parse_disk_info(element): disk_info = {} disk_info['type'] = element.get('type', 'file') disk_info['device'] = element.get('device', 'disk') driver = element.find('./driver') if driver is not None: disk_info['driver_name'] = driver.get('name') disk_info['driver_type'] = driver.get('type') source = element.find('./source') if source is not None: disk_info['source'] = source.get('file') if not disk_info['source']: disk_info['source'] = source.get('dev') if not disk_info['source']: disk_info['source'] = source.get('path') target = element.find('./target') if target is not None: disk_info['target_dev'] = target.get('dev') disk_info['target_bus'] = target.get('bus') return disk_info def _parse_nic_info(element): nic_info = {} nic_info['type'] = element.get('type', 'bridge') driver = element.find('./mac') if driver is not None: nic_info['mac'] = driver.get('address') source = element.find('./source') if source is not None: nic_info['source'] = source.get('bridge') target = element.find('./target') if target is not None: nic_info['target_dev'] = target.get('dev') return nic_info def disable_event_thread(self): """Disable nova libvirt driver event thread. The Nova libvirt driver includes a native thread which monitors the libvirt event channel. In a testing environment this becomes problematic because it means we've got a floating thread calling sleep(1) over the life of the unit test. Seems harmless? It's not, because we sometimes want to test things like retry loops that should have specific sleep paterns. An unlucky firing of the libvirt thread will cause a test failure. """ # because we are patching a method in a class MonkeyPatch doesn't # auto import correctly. Import explicitly otherwise the patching # may silently fail. import nova.virt.libvirt.host # noqa def evloop(*args, **kwargs): pass self.useFixture(fixtures.MockPatch( 'nova.virt.libvirt.host.Host._init_events', side_effect=evloop)) class libvirtError(Exception): """This class was copied and slightly modified from `libvirt-python:libvirt-override.py`. Since a test environment will use the real `libvirt-python` version of `libvirtError` if it's installed and not this fake, we need to maintain strict compatibility with the original class, including `__init__` args and instance-attributes. To create a libvirtError instance you should: # Create an unsupported error exception exc = libvirtError('my message') exc.err = (libvirt.VIR_ERR_NO_SUPPORT,) self.err is a tuple of form: (error_code, error_domain, error_message, error_level, str1, str2, str3, int1, int2) Alternatively, you can use the `make_libvirtError` convenience function to allow you to specify these attributes in one shot. """ def __init__(self, defmsg, conn=None, dom=None, net=None, pool=None, vol=None): Exception.__init__(self, defmsg) self.err = None def get_error_code(self): if self.err is None: return None return self.err[0] def get_error_domain(self): if self.err is None: return None return self.err[1] def get_error_message(self): if self.err is None: return None return self.err[2] def get_error_level(self): if self.err is None: return None return self.err[3] def get_str1(self): if self.err is None: return None return self.err[4] def get_str2(self): if self.err is None: return None return self.err[5] def get_str3(self): if self.err is None: return None return self.err[6] def get_int1(self): if self.err is None: return None return self.err[7] def get_int2(self): if self.err is None: return None return self.err[8] class NodeDevice(object): def __init__(self, connection, xml=None): self._connection = connection self._xml = xml if xml is not None: self._parse_xml(xml) def _parse_xml(self, xml): tree = etree.fromstring(xml) root = tree.find('.') self._name = root.find('name').text self._parent = root.find('parent').text def attach(self): pass def dettach(self): pass def reset(self): pass def XMLDesc(self, flags: int) -> str: return self._xml def parent(self) -> str: return self._parent def name(self) -> str: return self._name def listCaps(self) -> ty.List[str]: return [self.name().split('_')[0]] class Domain(object): def __init__(self, connection, xml, running=False, transient=False): self._connection = connection if running: connection._mark_running(self) self._state = running and VIR_DOMAIN_RUNNING or VIR_DOMAIN_SHUTOFF self._transient = transient self._def = self._parse_definition(xml) self._has_saved_state = False self._snapshots = {} self._id = self._connection._id_counter self._job_type = VIR_DOMAIN_JOB_UNBOUNDED def _parse_definition(self, xml): try: tree = etree.fromstring(xml) except etree.ParseError: raise make_libvirtError( libvirtError, "Invalid XML.", error_code=VIR_ERR_XML_DETAIL, error_domain=VIR_FROM_DOMAIN) definition = {} name = tree.find('./name') if name is not None: definition['name'] = name.text uuid_elem = tree.find('./uuid') if uuid_elem is not None: definition['uuid'] = uuid_elem.text else: definition['uuid'] = uuids.fake vcpu = tree.find('./vcpu') if vcpu is not None: definition['vcpu'] = int(vcpu.text) memory = tree.find('./memory') if memory is not None: definition['memory'] = int(memory.text) os = {} os_type = tree.find('./os/type') if os_type is not None: os['type'] = os_type.text os['arch'] = os_type.get('arch', self._connection.host_info.arch) os_kernel = tree.find('./os/kernel') if os_kernel is not None: os['kernel'] = os_kernel.text os_initrd = tree.find('./os/initrd') if os_initrd is not None: os['initrd'] = os_initrd.text os_cmdline = tree.find('./os/cmdline') if os_cmdline is not None: os['cmdline'] = os_cmdline.text os_boot = tree.find('./os/boot') if os_boot is not None: os['boot_dev'] = os_boot.get('dev') definition['os'] = os features = {} acpi = tree.find('./features/acpi') if acpi is not None: features['acpi'] = True definition['features'] = features cpu_pins = {} pins = tree.findall('./cputune/vcpupin') for pin in pins: cpu_pins[pin.get('vcpu')] = pin.get('cpuset') definition['cpu_pins'] = cpu_pins emulator_pin = tree.find('./cputune/emulatorpin') if emulator_pin is not None: definition['emulator_pin'] = emulator_pin.get('cpuset') memnodes = {} for node in tree.findall('./numatune/memnode'): memnodes[node.get('cellid')] = node.get('nodeset') definition['memnodes'] = memnodes devices = {} device_nodes = tree.find('./devices') if device_nodes is not None: disks_info = [] disks = device_nodes.findall('./disk') for disk in disks: disks_info += [_parse_disk_info(disk)] devices['disks'] = disks_info nics_info = [] nics = device_nodes.findall('./interface') for nic in nics: nic_info = {} nic_info['type'] = nic.get('type') mac = nic.find('./mac') if mac is not None: nic_info['mac'] = mac.get('address') source = nic.find('./source') if source is not None: if nic_info['type'] == 'network': nic_info['source'] = source.get('network') elif nic_info['type'] == 'bridge': nic_info['source'] = source.get('bridge') elif nic_info['type'] == 'hostdev': # is for VF when vnic_type # is direct. Add sriov vf pci information in nic_info address = source.find('./address') pci_type = address.get('type') pci_domain = address.get('domain').replace('0x', '') pci_bus = address.get('bus').replace('0x', '') pci_slot = address.get('slot').replace('0x', '') pci_function = address.get('function').replace( '0x', '') pci_device = "%s_%s_%s_%s_%s" % (pci_type, pci_domain, pci_bus, pci_slot, pci_function) nic_info['source'] = pci_device elif nic_info['type'] == 'vdpa': nic_info['source'] = source.get('dev') nics_info += [nic_info] devices['nics'] = nics_info hostdev_info = [] hostdevs = device_nodes.findall('./hostdev') for hostdev in hostdevs: address = hostdev.find('./source/address') # NOTE(gibi): only handle mdevs as pci is complicated dev_type = hostdev.get('type') if dev_type == 'mdev': hostdev_info.append({ 'type': dev_type, 'model': hostdev.get('model'), 'address_uuid': address.get('uuid') }) devices['hostdevs'] = hostdev_info vpmem_info = [] vpmems = device_nodes.findall('./memory') for vpmem in vpmems: model = vpmem.get('model') if model == 'nvdimm': source = vpmem.find('./source') target = vpmem.find('./target') path = source.find('./path').text alignsize = source.find('./alignsize').text size = target.find('./size').text node = target.find('./node').text vpmem_info.append({ 'path': path, 'size': size, 'alignsize': alignsize, 'node': node}) devices['vpmems'] = vpmem_info definition['devices'] = devices return definition def verify_hostdevs_interface_are_vfs(self): """Verify for interface type hostdev if the pci device is VF or not. """ error_message = ("Interface type hostdev is currently supported on " "SR-IOV Virtual Functions only") nics = self._def['devices'].get('nics', []) for nic in nics: if nic['type'] == 'hostdev': pci_device = nic['source'] pci_info_from_connection = self._connection.pci_info.devices[ pci_device] if 'phys_function' not in pci_info_from_connection.pci_device: raise make_libvirtError( libvirtError, error_message, error_code=VIR_ERR_CONFIG_UNSUPPORTED, error_domain=VIR_FROM_DOMAIN) def create(self): self.createWithFlags(0) def createWithFlags(self, flags): # FIXME: Not handling flags at the moment self.verify_hostdevs_interface_are_vfs() self._state = VIR_DOMAIN_RUNNING self._connection._mark_running(self) self._has_saved_state = False def isActive(self): return int(self._state == VIR_DOMAIN_RUNNING) def undefine(self): self._connection._undefine(self) def isPersistent(self): return True def undefineFlags(self, flags): self.undefine() if flags & VIR_DOMAIN_UNDEFINE_MANAGED_SAVE: if self.hasManagedSaveImage(0): self.managedSaveRemove() def destroy(self): self._state = VIR_DOMAIN_SHUTOFF self._connection._mark_not_running(self) def ID(self): return self._id def name(self): return self._def['name'] def UUIDString(self): return self._def['uuid'] def interfaceStats(self, device): return [10000242400, 1234, 0, 2, 213412343233, 34214234, 23, 3] def blockStats(self, device): return [2, 10000242400, 234, 2343424234, 34] def setTime(self, time=None, flags=0): pass def suspend(self): self._state = VIR_DOMAIN_PAUSED def shutdown(self): self._state = VIR_DOMAIN_SHUTDOWN self._connection._mark_not_running(self) def reset(self, flags): # FIXME: Not handling flags at the moment self._state = VIR_DOMAIN_RUNNING self._connection._mark_running(self) def info(self): return [self._state, int(self._def['memory']), int(self._def['memory']), self._def['vcpu'], 123456789] def migrateToURI3(self, dconnuri, params, flags): raise make_libvirtError( libvirtError, "Migration always fails for fake libvirt!", error_code=VIR_ERR_INTERNAL_ERROR, error_domain=VIR_FROM_QEMU) def migrateSetMaxDowntime(self, downtime): pass def attachDevice(self, xml): result = False if xml.startswith("
''' % dict(source_attr=source_attr, **disk) nics = '' for nic in self._def['devices']['nics']: if 'source' in nic: if nic['type'] == 'hostdev': nics += '''
''' % nic # noqa: E501 elif nic['type'] == 'vdpa': # TODO(stephenfin): In real life, this would actually have # an '
' element, but that requires information # about the host that we're not passing through yet nics += ''' ''' else: nics += '''
''' % nic # noqa: E501 hostdevs = '' for hostdev in self._def['devices']['hostdevs']: hostdevs += '''
''' % hostdev # noqa vpmems = '' for vpmem in self._def['devices']['vpmems']: vpmems += ''' %(path)s %(alignsize)s %(size)s %(node)s ''' % vpmem cputune = '' for vcpu, cpuset in self._def['cpu_pins'].items(): cputune += '' % (int(vcpu), cpuset) emulatorpin = None if 'emulator_pin' in self._def: emulatorpin = ('' % self._def['emulator_pin']) if cputune or emulatorpin: cputune = '%s%s' % (emulatorpin, cputune) numatune = '' for cellid, nodeset in self._def['memnodes'].items(): numatune += '' % (int(cellid), nodeset) numatune += '' % ','.join( self._def['memnodes'].values()) if numatune: numatune = '%s' % numatune serial_console = '' if CONF.serial_console.enabled: serial_console = """ """ return ''' %(name)s %(uuid)s %(memory)s %(memory)s %(vcpu)s hvm destroy restart restart %(cputune)s %(numatune)s /usr/bin/kvm %(disks)s
%(nics)s %(serial_console)s