Exemple #1
0
 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'))
Exemple #2
0
    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
Exemple #3
0
 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'))
Exemple #4
0
 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'))
Exemple #5
0
 def test_get_machine_type_no_conf_or_fallback(self):
     self.assertIsNone(libvirt_utils.get_default_machine_type('sparc'))
Exemple #6
0
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'))
Exemple #8
0
def get_default_machine_type(arch):
    return libvirt_utils.get_default_machine_type(arch)
Exemple #9
0
    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
Exemple #10
0
    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
Exemple #11
0
    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
Exemple #12
0
 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'))
Exemple #13
0
 def test_get_default_machine_type_empty(self):
     self.assertIsNone(libvirt_utils.get_default_machine_type('sparc'))
Exemple #14
0
 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'))