def test_get_machine_type_from_conf(self): self.useFixture( nova_fixtures.ConfPatcher( group="libvirt", hw_machine_type=['x86_64=q35', 'i686=legacy'])) self.assertEqual('q35', libvirt_utils.get_default_machine_type('x86_64'))
def _get_machine_types(self, arch, domain): """Get the machine types for this architecture for which we need to call getDomainCapabilities, i.e. the canonical machine types, and the default machine type (if it's not one of the canonical machine types). See the docstring for get_domain_capabilities() for an explanation of why we choose this set of machine types. """ # NOTE(aspiers): machine_type could be None here if nova # doesn't have a default machine type for this architecture. # See _add_to_domain_capabilities() below for how this is handled. mtypes = set([libvirt_utils.get_default_machine_type(arch)]) mtypes.update(domain.aliases.keys()) LOG.debug( "Getting domain capabilities for %(arch)s via " "machine types: %(mtypes)s", { 'arch': arch, 'mtypes': mtypes }) return mtypes
def test_get_machine_type_survives_invalid_conf(self): self.useFixture(nova_fixtures.ConfPatcher( group="libvirt", hw_machine_type=['x86_64=q35', 'foo'])) self.assertEqual('q35', libvirt_utils.get_default_machine_type('x86_64'))
def test_get_machine_type_missing_conf_and_fallback(self): self.useFixture(nova_fixtures.ConfPatcher( group="libvirt", hw_machine_type=['x86_64=q35', 'i686=legacy'])) self.assertIsNone(libvirt_utils.get_default_machine_type('sparc'))
def test_get_machine_type_no_conf_or_fallback(self): self.assertIsNone(libvirt_utils.get_default_machine_type('sparc'))
def get_default_machine_type(arch): return libvirt_utils.get_default_machine_type(arch)
def test_get_default_machine_type_empty(self): self.assertIsNone(libvirt_utils.get_default_machine_type('sparc'))
def get_default_machine_type(arch): return libvirt_utils.get_default_machine_type(arch)
def get_domain_capabilities(self): """Returns the capabilities you can request when creating a domain (VM) with that hypervisor, for various combinations of architecture and machine type. In this context the fuzzy word "hypervisor" implies QEMU binary, libvirt itself and the host config. libvirt provides this in order that callers can determine what the underlying emulator and/or libvirt is capable of, prior to creating a domain (for instance via virDomainCreateXML or virDomainDefineXML). However nova needs to know the capabilities much earlier, when the host's compute service is first initialised, in order that placement decisions can be made across many compute hosts. Therefore this is expected to be called during the init_host() phase of the driver lifecycle rather than just before booting an instance. This causes an additional complication since the Python binding for this libvirt API call requires the architecture and machine type to be provided. So in order to gain a full picture of the hypervisor's capabilities, technically we need to call it with the right parameters, once for each (architecture, machine_type) combination which we care about. However the libvirt experts have advised us that in practice the domain capabilities do not (yet, at least) vary enough across machine types to justify the cost of calling getDomainCapabilities() once for every single (architecture, machine_type) combination. In particular, SEV support isn't reported per-machine type, and since there are usually many machine types, we follow the advice of the experts that for now it's sufficient to call it once per host architecture: https://bugzilla.redhat.com/show_bug.cgi?id=1683471#c7 However, future domain capabilities might report SEV in a more fine-grained manner, and we also expect to use this method to detect other features, such as for gracefully handling machine types and potentially for detecting OVMF binaries. Therefore we memoize the results of the API calls in a nested dict where the top-level keys are architectures, and second-level keys are machine types, in order to allow easy expansion later. Whenever libvirt/QEMU are updated, cached domCapabilities would get outdated (because QEMU will contain new features and the capabilities will vary). However, this should not be a problem here, because when libvirt/QEMU gets updated, the nova-compute agent also needs restarting, at which point the memoization will vanish because it's not persisted to disk. Note: The result is cached in the member attribute _domain_caps. :returns: a nested dict of dicts which maps architectures to machine types to instances of config.LibvirtConfigDomainCaps representing the domain capabilities of the host for that arch and machine type: { arch: { machine_type: LibvirtConfigDomainCaps } } """ if self._domain_caps: return self._domain_caps domain_caps = defaultdict(dict) caps = self.get_capabilities() virt_type = CONF.libvirt.virt_type for guest in caps.guests: arch = guest.arch machine_type = \ libvirt_utils.get_default_machine_type(arch) or 'q35' emulator_bin = guest.emulator if virt_type in guest.domemulator: emulator_bin = guest.domemulator[virt_type] # It is expected that each <guest> will have a different # architecture, but it doesn't hurt to add a safety net to # avoid needlessly calling libvirt's API more times than # we need. if machine_type in domain_caps[arch]: continue domain_caps[arch][machine_type] = \ self._get_domain_capabilities(emulator_bin, arch, machine_type, virt_type) # NOTE(aspiers): Use a temporary variable to update the # instance variable atomically, otherwise if some API # calls succeeded and then one failed, we might # accidentally memoize a partial result. self._domain_caps = domain_caps return self._domain_caps
def get_domain_capabilities(self): """Returns the capabilities you can request when creating a domain (VM) with that hypervisor, for various combinations of architecture and machine type. In this context the fuzzy word "hypervisor" implies QEMU binary, libvirt itself and the host config. libvirt provides this in order that callers can determine what the underlying emulator and/or libvirt is capable of, prior to creating a domain (for instance via virDomainCreateXML or virDomainDefineXML). However nova needs to know the capabilities much earlier, when the host's compute service is first initialised, in order that placement decisions can be made across many compute hosts. Therefore this is expected to be called during the init_host() phase of the driver lifecycle rather than just before booting an instance. This causes an additional complication since the Python binding for this libvirt API call requires the architecture and machine type to be provided. So in order to gain a full picture of the hypervisor's capabilities, technically we need to call it with the right parameters, once for each (architecture, machine_type) combination which we care about. However the libvirt experts have advised us that in practice the domain capabilities do not (yet, at least) vary enough across machine types to justify the cost of calling getDomainCapabilities() once for every single (architecture, machine_type) combination. In particular, SEV support isn't reported per-machine type, and since there are usually many machine types, we follow the advice of the experts that for now it's sufficient to call it once per host architecture: https://bugzilla.redhat.com/show_bug.cgi?id=1683471#c7 However, future domain capabilities might report SEV in a more fine-grained manner, and we also expect to use this method to detect other features, such as for gracefully handling machine types and potentially for detecting OVMF binaries. Therefore we memoize the results of the API calls in a nested dict where the top-level keys are architectures, and second-level keys are machine types, in order to allow easy expansion later. Whenever libvirt/QEMU are updated, cached domCapabilities would get outdated (because QEMU will contain new features and the capabilities will vary). However, this should not be a problem here, because when libvirt/QEMU gets updated, the nova-compute agent also needs restarting, at which point the memoization will vanish because it's not persisted to disk. Note: The result is cached in the member attribute _domain_caps. :returns: a nested dict of dicts which maps architectures to machine types to instances of config.LibvirtConfigDomainCaps representing the domain capabilities of the host for that arch and machine type: { arch: { machine_type: LibvirtConfigDomainCaps } } """ if self._domain_caps: return self._domain_caps domain_caps = defaultdict(dict) caps = self.get_capabilities() virt_type = CONF.libvirt.virt_type for guest in caps.guests: arch = guest.arch machine_type = \ libvirt_utils.get_default_machine_type(arch) emulator_bin = guest.emulator if virt_type in guest.domemulator: emulator_bin = guest.domemulator[virt_type] # It is expected that each <guest> will have a different # architecture, but it doesn't hurt to add a safety net to # avoid needlessly calling libvirt's API more times than # we need. if machine_type and machine_type in domain_caps[arch]: continue # NOTE(aspiers): machine_type could be None here if nova # doesn't have a default machine type for this # architecture. In that case we pass a machine_type of # None to the libvirt API and rely on it choosing a # sensible default which will be returned in the <machine> # element. It could also be an alias like 'pc' rather # than a full machine type. # # NOTE(kchamart): Prior to libvirt v4.7.0 libvirt picked # its default machine type for x86, 'pc', as reported by # QEMU's default. From libvirt v4.7.0 onwards, libvirt # _explicitly_ declared the "preferred" default for x86 as # 'pc' (and appropriate values for other architectures), # and only uses QEMU's reported default (whatever that may # be) if 'pc' does not exist. This was done "to isolate # applications from hypervisor changes that may cause # incompatibilities" -- i.e. if, or when, QEMU changes its # default machine type to something else. Refer to this # libvirt commit: # # https://libvirt.org/git/?p=libvirt.git;a=commit;h=26cfb1a3 try: cap_obj = self._get_domain_capabilities( emulator_bin=emulator_bin, arch=arch, machine_type=machine_type, virt_type=virt_type) except libvirt.libvirtError as ex: # NOTE(sean-k-mooney): This can happen for several # reasons, but one common example is if you have # multiple QEMU emulators installed and you set # virt-type=kvm. In this case any non-native emulator, # e.g. AArch64 on an x86 host, will (correctly) raise # an exception as KVM cannot be used to accelerate CPU # instructions for non-native architectures. error_code = ex.get_error_code() LOG.debug( "Error from libvirt when retrieving domain capabilities " "for arch %(arch)s / virt_type %(virt_type)s / " "machine_type %(mach_type)s: " "[Error Code %(error_code)s]: %(exception)s", { 'arch': arch, 'virt_type': virt_type, 'mach_type': machine_type, 'error_code': error_code, 'exception': ex }) # Remove archs added by default dict lookup when checking # if the machine type has already been recoded. if arch in domain_caps: domain_caps.pop(arch) continue # Register the domain caps using the expanded form of # machine type returned by libvirt in the <machine> # element (e.g. pc-i440fx-2.11) if cap_obj.machine_type: domain_caps[arch][cap_obj.machine_type] = cap_obj else: # NOTE(aspiers): In theory this should never happen, # but better safe than sorry. LOG.warning( "libvirt getDomainCapabilities(" "emulator_bin=%(emulator_bin)s, arch=%(arch)s, " "machine_type=%(machine_type)s, virt_type=%(virt_type)s) " "returned null <machine> type", { 'emulator_bin': emulator_bin, 'arch': arch, 'machine_type': machine_type, 'virt_type': virt_type }) # And if we passed an alias, register the domain caps # under that too. if machine_type and machine_type != cap_obj.machine_type: domain_caps[arch][machine_type] = cap_obj cap_obj.machine_type_alias = machine_type # NOTE(aspiers): Use a temporary variable to update the # instance variable atomically, otherwise if some API # calls succeeded and then one failed, we might # accidentally memoize a partial result. self._domain_caps = domain_caps return self._domain_caps
def get_domain_capabilities(self): """Returns the capabilities you can request when creating a domain (VM) with that hypervisor, for various combinations of architecture and machine type. In this context the fuzzy word "hypervisor" implies QEMU binary, libvirt itself and the host config. libvirt provides this in order that callers can determine what the underlying emulator and/or libvirt is capable of, prior to creating a domain (for instance via virDomainCreateXML or virDomainDefineXML). However nova needs to know the capabilities much earlier, when the host's compute service is first initialised, in order that placement decisions can be made across many compute hosts. Therefore this is expected to be called during the init_host() phase of the driver lifecycle rather than just before booting an instance. This causes an additional complication since the Python binding for this libvirt API call requires the architecture and machine type to be provided. So in order to gain a full picture of the hypervisor's capabilities, technically we need to call it with the right parameters, once for each (architecture, machine_type) combination which we care about. However the libvirt experts have advised us that in practice the domain capabilities do not (yet, at least) vary enough across machine types to justify the cost of calling getDomainCapabilities() once for every single (architecture, machine_type) combination. In particular, SEV support isn't reported per-machine type, and since there are usually many machine types, we follow the advice of the experts that for now it's sufficient to call it once per host architecture: https://bugzilla.redhat.com/show_bug.cgi?id=1683471#c7 However, future domain capabilities might report SEV in a more fine-grained manner, and we also expect to use this method to detect other features, such as for gracefully handling machine types and potentially for detecting OVMF binaries. Therefore we memoize the results of the API calls in a nested dict where the top-level keys are architectures, and second-level keys are machine types, in order to allow easy expansion later. Whenever libvirt/QEMU are updated, cached domCapabilities would get outdated (because QEMU will contain new features and the capabilities will vary). However, this should not be a problem here, because when libvirt/QEMU gets updated, the nova-compute agent also needs restarting, at which point the memoization will vanish because it's not persisted to disk. Note: The result is cached in the member attribute _domain_caps. :returns: a nested dict of dicts which maps architectures to machine types to instances of config.LibvirtConfigDomainCaps representing the domain capabilities of the host for that arch and machine type: { arch: { machine_type: LibvirtConfigDomainCaps } } """ if self._domain_caps: return self._domain_caps domain_caps = defaultdict(dict) caps = self.get_capabilities() virt_type = CONF.libvirt.virt_type for guest in caps.guests: arch = guest.arch machine_type = \ libvirt_utils.get_default_machine_type(arch) or 'q35' emulator_bin = guest.emulator if virt_type in guest.domemulator: emulator_bin = guest.domemulator[virt_type] # It is expected that each <guest> will have a different # architecture, but it doesn't hurt to add a safety net to # avoid needlessly calling libvirt's API more times than # we need. if machine_type in domain_caps[arch]: continue domain_caps[arch][machine_type] = \ self._get_domain_capabilities(emulator_bin, arch, machine_type, virt_type) # NOTE(aspiers): Use a temporary variable to update the # instance variable atomically, otherwise if some API # calls succeeded and then one failed, we might # accidentally memoize a partial result. self._domain_caps = domain_caps return self._domain_caps
def test_get_default_machine_type_missing(self): self.useFixture(nova_fixtures.ConfPatcher( group="libvirt", hw_machine_type=['x86_64=q35', 'i686=legacy'])) self.assertIsNone(libvirt_utils.get_default_machine_type('sparc'))
def test_get_default_machine_type_empty(self): self.assertIsNone(libvirt_utils.get_default_machine_type('sparc'))
def test_get_default_machine_type(self): self.useFixture(nova_fixtures.ConfPatcher( group="libvirt", hw_machine_type=['x86_64=q35', 'i686=legacy'])) self.assertEqual('q35', libvirt_utils.get_default_machine_type('x86_64'))