Add method to get cpu traits
Add a method for libvirt driver to get cpu traits. This is used for compute nodes to report cpu traits to Placement. Change-Id: I9bd80adc244c64277d2d00e7d79c3002c8f9d57e blueprint: report-cpu-features-as-traits
This commit is contained in:
parent
4aad363fe4
commit
7637026b90
doc/source
nova
compute
tests
functional
unit
virt/libvirt
releasenotes/notes
@ -274,6 +274,15 @@ extra specs key as the key to be matched if no namespace is present; this
|
||||
action is highly discouraged because it conflicts with
|
||||
:ref:`AggregateInstanceExtraSpecsFilter` filter when you enable both filters.
|
||||
|
||||
Some virt drivers support reporting CPU traits to the Placement service. With that
|
||||
feature available, you should consider using traits in flavors instead of
|
||||
ComputeCapabilitiesFilter, because traits provide consistent naming for CPU
|
||||
features in some virt drivers and querying traits is efficient. For more detail, please see
|
||||
`Support Matrix <https://docs.openstack.org/nova/latest/user/support-matrix.html>`_,
|
||||
:ref:`Required traits <extra-specs-required-traits>`,
|
||||
:ref:`Forbidden traits <extra-specs-forbidden-traits>` and
|
||||
`Report CPU features to the Placement service <https://specs.openstack.org/openstack/nova-specs/specs/rocky/approved/report-cpu-features-as-traits.html>`_.
|
||||
|
||||
.. _ComputeFilter:
|
||||
|
||||
ComputeFilter
|
||||
|
@ -650,6 +650,8 @@ Secure Boot
|
||||
- ``disabled`` or ``optional``: (default) Disable Secure Boot for instances
|
||||
running with this flavor.
|
||||
|
||||
.. _extra-specs-required-traits:
|
||||
|
||||
Required traits
|
||||
Added in the 17.0.0 Queens release.
|
||||
|
||||
@ -677,6 +679,8 @@ Required traits
|
||||
|
||||
Traits can be managed using the `osc-placement plugin`_.
|
||||
|
||||
.. _extra-specs-forbidden-traits:
|
||||
|
||||
Forbidden traits
|
||||
Added in the 18.0.0 Rocky release.
|
||||
|
||||
|
@ -1527,3 +1527,25 @@ driver.ironic=missing
|
||||
driver.libvirt-vz-vm=missing
|
||||
driver.libvirt-vz-ct=missing
|
||||
driver.powervm=missing
|
||||
|
||||
[operation.report-cpu-traits]
|
||||
title=Report CPU traits
|
||||
status=optional
|
||||
notes=The report CPU traits feature in OpenStack allows a Nova node to report
|
||||
its CPU traits according to CPU mode configuration. This gives users the ability
|
||||
to boot instances based on desired CPU traits.
|
||||
cli=
|
||||
driver.xenserver=missing
|
||||
driver.libvirt-kvm-x86=complete
|
||||
driver.libvirt-kvm-aarch64=unknown
|
||||
driver.libvirt-kvm-ppc64=complete
|
||||
driver.libvirt-kvm-s390x=missing
|
||||
driver.libvirt-qemu-x86=complete
|
||||
driver.libvirt-lxc=missing
|
||||
driver.libvirt-xen=missing
|
||||
driver.vmware=missing
|
||||
driver.hyperv=missing
|
||||
driver.ironic=missing
|
||||
driver.libvirt-vz-vm=missing
|
||||
driver.libvirt-vz-ct=missing
|
||||
driver.powervm=missing
|
||||
|
@ -558,6 +558,8 @@ class ProviderTree(object):
|
||||
to be affected.
|
||||
:param traits: String names of traits to be added.
|
||||
"""
|
||||
if not traits:
|
||||
return
|
||||
with self.lock:
|
||||
provider = self._find_with_lock(name_or_uuid)
|
||||
final_traits = provider.traits | set(traits)
|
||||
@ -570,6 +572,8 @@ class ProviderTree(object):
|
||||
to be affected.
|
||||
:param traits: String names of traits to be removed.
|
||||
"""
|
||||
if not traits:
|
||||
return
|
||||
with self.lock:
|
||||
provider = self._find_with_lock(name_or_uuid)
|
||||
final_traits = provider.traits - set(traits)
|
||||
|
@ -421,6 +421,9 @@ class ProviderUsageBaseTestCase(test.TestCase, InstanceHelperMixin):
|
||||
return self.placement_api.get(
|
||||
'/resource_providers', version='1.14').body['resource_providers']
|
||||
|
||||
def _create_trait(self, trait):
|
||||
return self.placement_api.put('/traits/%s' % trait, {}, version='1.6')
|
||||
|
||||
def _get_provider_traits(self, provider_uuid):
|
||||
return self.placement_api.get(
|
||||
'/resource_providers/%s/traits' % provider_uuid,
|
||||
|
62
nova/tests/functional/libvirt/test_report_cpu_traits.py
Normal file
62
nova/tests/functional/libvirt/test_report_cpu_traits.py
Normal file
@ -0,0 +1,62 @@
|
||||
# Copyright (c) 2018 Intel, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 fixtures
|
||||
|
||||
from nova import conf
|
||||
from nova.tests.functional import integrated_helpers
|
||||
from nova.tests.unit.virt.libvirt import fakelibvirt
|
||||
|
||||
CONF = conf.CONF
|
||||
|
||||
|
||||
class LibvirtReportTraitsTests(integrated_helpers.ProviderUsageBaseTestCase):
|
||||
compute_driver = 'libvirt.LibvirtDriver'
|
||||
|
||||
def setUp(self):
|
||||
super(LibvirtReportTraitsTests, self).setUp()
|
||||
self.useFixture(fixtures.MonkeyPatch(
|
||||
'nova.virt.libvirt.driver.libvirt',
|
||||
fakelibvirt))
|
||||
self.useFixture(fixtures.MonkeyPatch('nova.virt.libvirt.host.libvirt',
|
||||
fakelibvirt))
|
||||
self.useFixture(
|
||||
fixtures.MockPatch(
|
||||
'nova.virt.libvirt.driver.LibvirtDriver.init_host'))
|
||||
|
||||
self.useFixture(
|
||||
fixtures.MockPatch('nova.virt.libvirt.utils.get_fs_info'))
|
||||
|
||||
self.assertEqual([], self._get_all_providers())
|
||||
self.compute = self._start_compute(CONF.host)
|
||||
nodename = self.compute.manager._get_nodename(None)
|
||||
self.host_uuid = self._get_provider_uuid_by_host(nodename)
|
||||
|
||||
def test_report_cpu_traits(self):
|
||||
# Test CPU traits reported on initial node startup, these specific
|
||||
# trait values are coming from fakelibvirt's baselineCPU result.
|
||||
self.assertItemsEqual(['HW_CPU_X86_VMX', 'HW_CPU_X86_AESNI'],
|
||||
self._get_provider_traits(self.host_uuid))
|
||||
|
||||
self._create_trait('CUSTOM_TRAITS')
|
||||
new_traits = ['CUSTOM_TRAITS', 'HW_CPU_X86_AVX']
|
||||
self._set_provider_traits(self.host_uuid, new_traits)
|
||||
self._run_periodics()
|
||||
# HW_CPU_X86_AVX is filtered out because nova-compute owns CPU traits
|
||||
# and it's not in the baseline for the host.
|
||||
self.assertItemsEqual(
|
||||
['HW_CPU_X86_VMX', 'HW_CPU_X86_AESNI', 'CUSTOM_TRAITS'],
|
||||
self._get_provider_traits(self.host_uuid)
|
||||
)
|
@ -562,6 +562,12 @@ class TestProviderTree(test.NoDBTestCase):
|
||||
cn = self.compute_node1
|
||||
pt = self._pt_with_cns()
|
||||
self.assertEqual(set([]), pt.data(cn.uuid).traits)
|
||||
# Test adding with no trait provided for a bogus provider
|
||||
pt.add_traits('bogus-uuid')
|
||||
self.assertEqual(
|
||||
set([]),
|
||||
pt.data(cn.uuid).traits
|
||||
)
|
||||
# Add a couple of traits
|
||||
pt.add_traits(cn.uuid, "HW_GPU_API_DIRECT3D_V7_0", "HW_NIC_OFFLOAD_SG")
|
||||
self.assertEqual(
|
||||
@ -570,6 +576,12 @@ class TestProviderTree(test.NoDBTestCase):
|
||||
# set() behavior: add a trait that's already there, and one that's not.
|
||||
# The unrelated one is unaffected.
|
||||
pt.add_traits(cn.uuid, "HW_GPU_API_DIRECT3D_V7_0", "HW_CPU_X86_AVX")
|
||||
self.assertEqual(
|
||||
set(["HW_GPU_API_DIRECT3D_V7_0", "HW_NIC_OFFLOAD_SG",
|
||||
"HW_CPU_X86_AVX"]),
|
||||
pt.data(cn.uuid).traits)
|
||||
# Test removing with no trait provided for a bogus provider
|
||||
pt.remove_traits('bogus-uuid')
|
||||
self.assertEqual(
|
||||
set(["HW_GPU_API_DIRECT3D_V7_0", "HW_NIC_OFFLOAD_SG",
|
||||
"HW_CPU_X86_AVX"]),
|
||||
|
@ -25,6 +25,7 @@ disk_backing_files = {}
|
||||
disk_type = "qcow2"
|
||||
|
||||
RESIZE_SNAPSHOT_NAME = libvirt_utils.RESIZE_SNAPSHOT_NAME
|
||||
CPU_TRAITS_MAPPING = libvirt_utils.CPU_TRAITS_MAPPING
|
||||
|
||||
|
||||
def create_image(disk_format, path, size):
|
||||
@ -172,3 +173,7 @@ def get_arch(image_meta):
|
||||
|
||||
def version_to_string(version):
|
||||
return libvirt_utils.version_to_string(version)
|
||||
|
||||
|
||||
def cpu_features_to_traits(features):
|
||||
return libvirt_utils.cpu_features_to_traits(features)
|
||||
|
@ -115,6 +115,7 @@ 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
|
||||
|
@ -351,6 +351,98 @@ _fake_cpu_info = {
|
||||
|
||||
eph_default_ext = utils.get_hash_str(nova.privsep.fs._DEFAULT_FILE_SYSTEM)[:7]
|
||||
|
||||
_fake_qemu64_cpu_feature = """
|
||||
<cpu mode='custom' match='exact'>
|
||||
<model fallback='forbid'>qemu64</model>
|
||||
<feature policy='require' name='svm'/>
|
||||
<feature policy='require' name='lm'/>
|
||||
<feature policy='require' name='nx'/>
|
||||
<feature policy='require' name='syscall'/>
|
||||
<feature policy='require' name='cx16'/>
|
||||
<feature policy='require' name='pni'/>
|
||||
<feature policy='require' name='sse2'/>
|
||||
<feature policy='require' name='sse'/>
|
||||
<feature policy='require' name='fxsr'/>
|
||||
<feature policy='require' name='mmx'/>
|
||||
<feature policy='require' name='clflush'/>
|
||||
<feature policy='require' name='pse36'/>
|
||||
<feature policy='require' name='pat'/>
|
||||
<feature policy='require' name='cmov'/>
|
||||
<feature policy='require' name='mca'/>
|
||||
<feature policy='require' name='pge'/>
|
||||
<feature policy='require' name='mtrr'/>
|
||||
<feature policy='require' name='sep'/>
|
||||
<feature policy='require' name='apic'/>
|
||||
<feature policy='require' name='cx8'/>
|
||||
<feature policy='require' name='mce'/>
|
||||
<feature policy='require' name='pae'/>
|
||||
<feature policy='require' name='msr'/>
|
||||
<feature policy='require' name='tsc'/>
|
||||
<feature policy='require' name='pse'/>
|
||||
<feature policy='require' name='de'/>
|
||||
<feature policy='require' name='fpu'/>
|
||||
</cpu>
|
||||
"""
|
||||
|
||||
_fake_broadwell_cpu_feature = """
|
||||
<cpu mode='custom' match='exact'>
|
||||
<model fallback='forbid'>Broadwell-noTSX</model>
|
||||
<vendor>Intel</vendor>
|
||||
<feature policy='require' name='smap'/>
|
||||
<feature policy='require' name='adx'/>
|
||||
<feature policy='require' name='rdseed'/>
|
||||
<feature policy='require' name='invpcid'/>
|
||||
<feature policy='require' name='erms'/>
|
||||
<feature policy='require' name='bmi2'/>
|
||||
<feature policy='require' name='smep'/>
|
||||
<feature policy='require' name='avx2'/>
|
||||
<feature policy='require' name='bmi1'/>
|
||||
<feature policy='require' name='fsgsbase'/>
|
||||
<feature policy='require' name='3dnowprefetch'/>
|
||||
<feature policy='require' name='lahf_lm'/>
|
||||
<feature policy='require' name='lm'/>
|
||||
<feature policy='require' name='rdtscp'/>
|
||||
<feature policy='require' name='nx'/>
|
||||
<feature policy='require' name='syscall'/>
|
||||
<feature policy='require' name='avx'/>
|
||||
<feature policy='require' name='xsave'/>
|
||||
<feature policy='require' name='aes'/>
|
||||
<feature policy='require' name='tsc-deadline'/>
|
||||
<feature policy='require' name='popcnt'/>
|
||||
<feature policy='require' name='movbe'/>
|
||||
<feature policy='require' name='x2apic'/>
|
||||
<feature policy='require' name='sse4.2'/>
|
||||
<feature policy='require' name='sse4.1'/>
|
||||
<feature policy='require' name='pcid'/>
|
||||
<feature policy='require' name='cx16'/>
|
||||
<feature policy='require' name='fma'/>
|
||||
<feature policy='require' name='ssse3'/>
|
||||
<feature policy='require' name='pclmuldq'/>
|
||||
<feature policy='require' name='pni'/>
|
||||
<feature policy='require' name='sse2'/>
|
||||
<feature policy='require' name='sse'/>
|
||||
<feature policy='require' name='fxsr'/>
|
||||
<feature policy='require' name='mmx'/>
|
||||
<feature policy='require' name='clflush'/>
|
||||
<feature policy='require' name='pse36'/>
|
||||
<feature policy='require' name='pat'/>
|
||||
<feature policy='require' name='cmov'/>
|
||||
<feature policy='require' name='mca'/>
|
||||
<feature policy='require' name='pge'/>
|
||||
<feature policy='require' name='mtrr'/>
|
||||
<feature policy='require' name='sep'/>
|
||||
<feature policy='require' name='apic'/>
|
||||
<feature policy='require' name='cx8'/>
|
||||
<feature policy='require' name='mce'/>
|
||||
<feature policy='require' name='pae'/>
|
||||
<feature policy='require' name='msr'/>
|
||||
<feature policy='require' name='tsc'/>
|
||||
<feature policy='require' name='pse'/>
|
||||
<feature policy='require' name='de'/>
|
||||
<feature policy='require' name='fpu'/>
|
||||
</cpu>
|
||||
"""
|
||||
|
||||
|
||||
def eph_name(size):
|
||||
return ('ephemeral_%(size)s_%(ext)s' %
|
||||
@ -17770,6 +17862,7 @@ class TestUpdateProviderTree(test.NoDBTestCase):
|
||||
vcpus = 24
|
||||
memory_mb = 1024
|
||||
disk_gb = 200
|
||||
cpu_traits = {t: False for t in libvirt_utils.CPU_TRAITS_MAPPING.values()}
|
||||
|
||||
def setUp(self):
|
||||
super(TestUpdateProviderTree, self).setUp()
|
||||
@ -17801,6 +17894,8 @@ class TestUpdateProviderTree(test.NoDBTestCase):
|
||||
return pt
|
||||
|
||||
self.pt = _pt_with_cn_rp_and_shared_rp()
|
||||
self.cpu_traits['HW_CPU_X86_AVX512F'] = True
|
||||
self.cpu_traits['HW_CPU_X86_BMI'] = True
|
||||
|
||||
def _get_inventory(self):
|
||||
return {
|
||||
@ -17824,6 +17919,8 @@ class TestUpdateProviderTree(test.NoDBTestCase):
|
||||
},
|
||||
}
|
||||
|
||||
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver._get_cpu_traits',
|
||||
new=mock.Mock(return_value=cpu_traits))
|
||||
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver._get_vgpu_total')
|
||||
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver._get_local_gb_info',
|
||||
return_value={'total': disk_gb})
|
||||
@ -17841,6 +17938,8 @@ class TestUpdateProviderTree(test.NoDBTestCase):
|
||||
self._test_update_provider_tree()
|
||||
self.assertEqual(self._get_inventory(),
|
||||
(self.pt.data(self.cn_rp.uuid)).inventory)
|
||||
self.assertEqual(set(['HW_CPU_X86_AVX512F', 'HW_CPU_X86_BMI']),
|
||||
self.pt.data(self.cn_rp.uuid).traits)
|
||||
|
||||
def test_update_provider_tree_with_vgpus(self):
|
||||
self._test_update_provider_tree(total_vgpus=8)
|
||||
@ -17902,8 +18001,23 @@ class TestUpdateProviderTree(test.NoDBTestCase):
|
||||
self.assertEqual(self._get_inventory(),
|
||||
(self.pt.data(self.cn_rp.uuid)).inventory)
|
||||
|
||||
def test_update_provider_tree_with_cpu_traits(self):
|
||||
# These two traits should be unset when update_provider_tree is called
|
||||
self.pt.add_traits(self.cn_rp.uuid, 'HW_CPU_X86_VMX', 'HW_CPU_X86_XOP')
|
||||
self._test_update_provider_tree()
|
||||
self.assertEqual(set(['HW_CPU_X86_AVX512F', 'HW_CPU_X86_BMI']),
|
||||
self.pt.data(self.cn_rp.uuid).traits)
|
||||
|
||||
class LibvirtDriverTestCase(test.NoDBTestCase):
|
||||
|
||||
class TraitsComparisonMixin(object):
|
||||
|
||||
def assertTraitsEqual(self, expected, actual):
|
||||
exp = {t: t in expected
|
||||
for t in libvirt_utils.CPU_TRAITS_MAPPING.values()}
|
||||
self.assertEqual(exp, actual)
|
||||
|
||||
|
||||
class LibvirtDriverTestCase(test.NoDBTestCase, TraitsComparisonMixin):
|
||||
"""Test for nova.virt.libvirt.libvirt_driver.LibvirtDriver."""
|
||||
def setUp(self):
|
||||
super(LibvirtDriverTestCase, self).setUp()
|
||||
@ -20469,6 +20583,254 @@ class LibvirtDriverTestCase(test.NoDBTestCase):
|
||||
self.assertRaises(test.TestingException,
|
||||
self._test_detach_mediated_devices, exc)
|
||||
|
||||
def test_cpu_traits_with_passthrough_mode(self):
|
||||
"""Test getting CPU traits when cpu_mmode is 'host-passthrough', traits
|
||||
are calculated from fakelibvirt's baseline CPU features.
|
||||
"""
|
||||
self.flags(cpu_mode='host-passthrough', group='libvirt')
|
||||
self.assertTraitsEqual(['HW_CPU_X86_AESNI', 'HW_CPU_X86_VMX'],
|
||||
self.drvr._get_cpu_traits())
|
||||
|
||||
@mock.patch('nova.virt.libvirt.host.libvirt.Connection.baselineCPU')
|
||||
def test_cpu_traits_with_mode_none(self, mock_baseline):
|
||||
"""Test getting CPU traits when cpu_mode is 'none', traits are
|
||||
calculated from _fake_qemu64_cpu_features.
|
||||
"""
|
||||
self.flags(cpu_mode='none', group='libvirt')
|
||||
mock_baseline.return_value = _fake_qemu64_cpu_feature
|
||||
self.assertTraitsEqual(['HW_CPU_X86_SSE', 'HW_CPU_X86_SVM',
|
||||
'HW_CPU_X86_MMX', 'HW_CPU_X86_SSE2'],
|
||||
self.drvr._get_cpu_traits())
|
||||
|
||||
mock_baseline.assert_called_with([u'''<cpu>
|
||||
<arch>x86_64</arch>
|
||||
<model>qemu64</model>
|
||||
<vendor>Intel</vendor>
|
||||
<topology sockets="1" cores="2" threads="1"/>
|
||||
</cpu>
|
||||
'''], 1)
|
||||
|
||||
@mock.patch('nova.virt.libvirt.host.libvirt.Connection.baselineCPU')
|
||||
def test_cpu_traits_with_mode_custom(self, mock_baseline):
|
||||
"""Test getting CPU traits when cpu_mode is 'custom' and cpu_model is
|
||||
'Broadwell-noTSX', traits are calculated from
|
||||
_fake_broadwell_cpu_features.
|
||||
"""
|
||||
self.flags(cpu_mode='custom',
|
||||
cpu_model='Broadwell-noTSX',
|
||||
group='libvirt')
|
||||
mock_baseline.return_value = _fake_broadwell_cpu_feature
|
||||
|
||||
self.assertTraitsEqual(
|
||||
[
|
||||
'HW_CPU_X86_BMI2',
|
||||
'HW_CPU_X86_AVX2',
|
||||
'HW_CPU_X86_BMI',
|
||||
'HW_CPU_X86_AVX',
|
||||
'HW_CPU_X86_AESNI',
|
||||
'HW_CPU_X86_SSE42',
|
||||
'HW_CPU_X86_SSE41',
|
||||
'HW_CPU_X86_FMA3',
|
||||
'HW_CPU_X86_SSSE3',
|
||||
'HW_CPU_X86_CLMUL',
|
||||
'HW_CPU_X86_SSE2',
|
||||
'HW_CPU_X86_SSE',
|
||||
'HW_CPU_X86_MMX'
|
||||
], self.drvr._get_cpu_traits()
|
||||
)
|
||||
mock_baseline.assert_called_with([u'''<cpu>
|
||||
<arch>x86_64</arch>
|
||||
<model>Broadwell-noTSX</model>
|
||||
<vendor>Intel</vendor>
|
||||
<topology sockets="1" cores="2" threads="1"/>
|
||||
</cpu>
|
||||
'''], 1)
|
||||
|
||||
@mock.patch('nova.virt.libvirt.host.libvirt.Connection.baselineCPU')
|
||||
def test_cpu_traits_with_no_baseline_support(self, mock_baseline):
|
||||
"""Test getting CPU traits when baseline call is not supported."""
|
||||
self.flags(cpu_mode='none', group='libvirt')
|
||||
not_supported_exc = fakelibvirt.make_libvirtError(
|
||||
fakelibvirt.libvirtError,
|
||||
'this function is not supported by the connection driver',
|
||||
error_code=fakelibvirt.VIR_ERR_NO_SUPPORT)
|
||||
mock_baseline.side_effect = not_supported_exc
|
||||
self.assertTraitsEqual([], self.drvr._get_cpu_traits())
|
||||
|
||||
@mock.patch('nova.virt.libvirt.host.libvirt.Connection.getCapabilities')
|
||||
@mock.patch('nova.virt.libvirt.host.libvirt.Connection.baselineCPU')
|
||||
def test_cpu_traits_on_s390x(self, mock_baseline, mock_cap):
|
||||
"""Test getting CPU traits on s390x, baseline call is not supported on
|
||||
the platform.
|
||||
"""
|
||||
self.flags(cpu_mode='none', group='libvirt')
|
||||
|
||||
mock_cap.return_value = """
|
||||
<capabilities>
|
||||
<host>
|
||||
<uuid>cef19ce0-0ca2-11df-855d-b19fbce37686</uuid>
|
||||
<cpu>
|
||||
<arch>s390x</arch>
|
||||
<topology sockets='1' cores='6' threads='1'/>
|
||||
<pages unit='KiB' size='4' />
|
||||
<pages unit='KiB' size='1024' />
|
||||
</cpu>
|
||||
</host>
|
||||
</capabilities>
|
||||
"""
|
||||
|
||||
not_supported_exc = fakelibvirt.make_libvirtError(
|
||||
fakelibvirt.libvirtError,
|
||||
'this function is not supported by the connection driver: cannot'
|
||||
' compute baseline CPU',
|
||||
error_code=fakelibvirt.VIR_ERR_NO_SUPPORT)
|
||||
missing_model_exc = fakelibvirt.make_libvirtError(
|
||||
fakelibvirt.libvirtError,
|
||||
'XML error: Missing CPU model name',
|
||||
error_code=fakelibvirt.VIR_ERR_XML_ERROR)
|
||||
|
||||
# model the libvirt behavior on s390x
|
||||
def mocked_baseline(cpu_xml, *args):
|
||||
xml = cpu_xml[0]
|
||||
if "<model>" in xml:
|
||||
raise not_supported_exc
|
||||
else:
|
||||
raise missing_model_exc
|
||||
mock_baseline.side_effect = mocked_baseline
|
||||
|
||||
self.assertTraitsEqual([], self.drvr._get_cpu_traits())
|
||||
|
||||
def test_cpu_traits_with_invalid_virt_type(self):
|
||||
"""Test getting CPU traits when using a virt_type that doesn't support
|
||||
the feature, only kvm and qemu supports reporting CPU traits.
|
||||
"""
|
||||
self.flags(cpu_mode='custom',
|
||||
cpu_model='IvyBridge',
|
||||
virt_type='lxc',
|
||||
group='libvirt'
|
||||
)
|
||||
self.assertRaises(exception.Invalid, self.drvr._get_cpu_traits)
|
||||
|
||||
@mock.patch('nova.virt.libvirt.host.libvirt.Connection.getCapabilities')
|
||||
@mock.patch('nova.virt.libvirt.utils.cpu_features_to_traits')
|
||||
def test_cpu_traits_with_mode_passthrough_and_extra_flags(
|
||||
self, mock_to_traits, mock_cap):
|
||||
"""Test if extra flags are accounted when cpu_mode is set to
|
||||
host-passthrough.
|
||||
"""
|
||||
self.flags(cpu_mode='host-passthrough',
|
||||
cpu_model_extra_flags='PCID',
|
||||
group='libvirt')
|
||||
mock_cap.return_value = """
|
||||
<capabilities>
|
||||
<host>
|
||||
<uuid>cef19ce0-0ca2-11df-855d-b19fbce37686</uuid>
|
||||
<cpu>
|
||||
<arch>IvyBridge</arch>
|
||||
<topology sockets='1' cores='2' threads='2'/>
|
||||
<feature policy='require' name='erms'/>
|
||||
<pages unit='KiB' size='4' />
|
||||
<pages unit='KiB' size='1024' />
|
||||
</cpu>
|
||||
</host>
|
||||
</capabilities>
|
||||
"""
|
||||
self.drvr._get_cpu_traits()
|
||||
self.assertItemsEqual(['pcid', 'erms'], mock_to_traits.call_args[0][0])
|
||||
|
||||
@mock.patch('nova.virt.libvirt.host.libvirt.Connection.baselineCPU')
|
||||
@mock.patch('nova.virt.libvirt.utils.cpu_features_to_traits')
|
||||
def test_cpu_traits_with_mode_custom_and_extra_flags(self, mock_to_traits,
|
||||
mock_baseline):
|
||||
"""Test if extra flags are accounted when cpu_mode is set to custom.
|
||||
"""
|
||||
self.flags(cpu_mode='custom',
|
||||
cpu_model='IvyBridge',
|
||||
cpu_model_extra_flags='PCID',
|
||||
group='libvirt')
|
||||
|
||||
mock_baseline.return_value = """
|
||||
<cpu mode='custom' match='exact'>
|
||||
<model fallback='forbid'>IvyBridge</model>
|
||||
<vendor>Intel</vendor>
|
||||
<feature policy='require' name='erms'/>
|
||||
<feature policy='require' name='pcid'/>
|
||||
</cpu>
|
||||
"""
|
||||
self.drvr._get_cpu_traits()
|
||||
mock_baseline.assert_called_with([u'''<cpu>
|
||||
<arch>x86_64</arch>
|
||||
<model>IvyBridge</model>
|
||||
<vendor>Intel</vendor>
|
||||
<topology sockets="1" cores="2" threads="1"/>
|
||||
<feature name="pcid"/>
|
||||
</cpu>
|
||||
'''], 1)
|
||||
self.assertItemsEqual(['pcid', 'erms'], mock_to_traits.call_args[0][0])
|
||||
|
||||
@mock.patch('nova.virt.libvirt.host.libvirt.Connection.baselineCPU')
|
||||
@mock.patch('nova.virt.libvirt.utils.cpu_features_to_traits')
|
||||
def test_cpu_traits_with_mode_not_set_and_extra_flags(self, mock_to_traits,
|
||||
mock_baseline):
|
||||
"""Test if extra flags are accounted when cpu_mode is not set."""
|
||||
self.flags(cpu_mode=None,
|
||||
cpu_model_extra_flags='PCID',
|
||||
virt_type='kvm',
|
||||
group='libvirt'
|
||||
)
|
||||
mock_baseline.return_value = """
|
||||
<cpu mode='custom' match='exact'>
|
||||
<model fallback='forbid'>IvyBridge</model>
|
||||
<vendor>Intel</vendor>
|
||||
<feature policy='require' name='erms'/>
|
||||
</cpu>
|
||||
"""
|
||||
self.drvr._get_cpu_traits()
|
||||
self.assertItemsEqual(['pcid', 'erms'], mock_to_traits.call_args[0][0])
|
||||
|
||||
def test_cpu_traits_with_mode_none_and_invalid_virt_type(self):
|
||||
"""Test case that cpu mode is none and virt_type is neither kvm nor
|
||||
qemu.
|
||||
"""
|
||||
self.flags(cpu_mode='none',
|
||||
virt_type='lxc',
|
||||
group='libvirt')
|
||||
self.assertIsNone(self.drvr._get_cpu_traits())
|
||||
|
||||
@mock.patch('nova.virt.libvirt.host.libvirt.Connection.getCapabilities')
|
||||
@mock.patch('nova.virt.libvirt.host.libvirt.Connection.baselineCPU')
|
||||
def test_cpu_traits_with_mode_none_on_power(self, mock_baseline, mock_cap):
|
||||
"""Test case that cpu mode is none on Power machines."""
|
||||
self.flags(cpu_mode='none', virt_type='kvm', group='libvirt')
|
||||
mock_cap.return_value = '''
|
||||
<capabilities>
|
||||
<host>
|
||||
<uuid>1f71d34a-7c89-45cf-95ce-3df20fc6b936</uuid>
|
||||
<cpu>
|
||||
<model>POWER8</model>
|
||||
<vendor>IBM</vendor>
|
||||
<arch>ppc64le</arch>
|
||||
<topology sockets='1' cores='5' threads='1'/>
|
||||
<pages unit='KiB' size='64'/>
|
||||
</cpu>
|
||||
</host>
|
||||
</capabilities>
|
||||
'''
|
||||
mock_baseline.return_value = '''
|
||||
<cpu>
|
||||
<model>POWER8</model>
|
||||
<vendor>IBM</vendor>
|
||||
</cpu>
|
||||
'''
|
||||
self.drvr._get_cpu_traits()
|
||||
mock_baseline.assert_called_with([u'''<cpu>
|
||||
<arch>ppc64le</arch>
|
||||
<model>POWER8</model>
|
||||
<vendor>IBM</vendor>
|
||||
<topology sockets="1" cores="5" threads="1"/>
|
||||
</cpu>
|
||||
'''], 1)
|
||||
|
||||
|
||||
class LibvirtVolumeUsageTestCase(test.NoDBTestCase):
|
||||
"""Test for LibvirtDriver.get_all_volume_usage."""
|
||||
|
@ -43,6 +43,7 @@ import time
|
||||
import uuid
|
||||
|
||||
from castellan import key_manager
|
||||
from copy import deepcopy
|
||||
import eventlet
|
||||
from eventlet import greenthread
|
||||
from eventlet import tpool
|
||||
@ -6379,8 +6380,8 @@ class LibvirtDriver(driver.ComputeDriver):
|
||||
self.firewall_driver.refresh_instance_security_rules(instance)
|
||||
|
||||
def update_provider_tree(self, provider_tree, nodename):
|
||||
"""Update a ProviderTree object with current resource provider and
|
||||
inventory information.
|
||||
"""Update a ProviderTree object with current resource provider,
|
||||
inventory information and CPU traits.
|
||||
|
||||
:param nova.compute.provider_tree.ProviderTree provider_tree:
|
||||
A nova.compute.provider_tree.ProviderTree object representing all
|
||||
@ -6453,6 +6454,16 @@ class LibvirtDriver(driver.ComputeDriver):
|
||||
|
||||
provider_tree.update_inventory(nodename, result)
|
||||
|
||||
traits = self._get_cpu_traits()
|
||||
if traits is not None:
|
||||
# _get_cpu_traits returns a dict of trait names mapped to boolean
|
||||
# values. Add traits equal to True to provider tree, remove
|
||||
# those False traits from provider tree.
|
||||
traits_to_add = [t for t in traits if traits[t]]
|
||||
traits_to_remove = set(traits) - set(traits_to_add)
|
||||
provider_tree.add_traits(nodename, *traits_to_add)
|
||||
provider_tree.remove_traits(nodename, *traits_to_remove)
|
||||
|
||||
def get_available_resource(self, nodename):
|
||||
"""Retrieve resource information.
|
||||
|
||||
@ -8911,3 +8922,70 @@ class LibvirtDriver(driver.ComputeDriver):
|
||||
nova.privsep.fs.FS_FORMAT_EXT3,
|
||||
nova.privsep.fs.FS_FORMAT_EXT4,
|
||||
nova.privsep.fs.FS_FORMAT_XFS]
|
||||
|
||||
def _get_cpu_traits(self):
|
||||
"""Get CPU traits of VMs based on guest CPU model config:
|
||||
1. if mode is 'host-model' or 'host-passthrough', use host's
|
||||
CPU features.
|
||||
2. if mode is None, choose a default CPU model based on CPU
|
||||
architecture.
|
||||
3. if mode is 'custom', use cpu_model to generate CPU features.
|
||||
The code also accounts for cpu_model_extra_flags configuration when
|
||||
cpu_mode is 'host-model', 'host-passthrough' or 'custom', this
|
||||
ensures user specified CPU feature flags to be included.
|
||||
:return: A dict of trait names mapped to boolean values or None.
|
||||
"""
|
||||
cpu = self._get_guest_cpu_model_config()
|
||||
if not cpu:
|
||||
LOG.info('The current libvirt hypervisor %(virt_type)s '
|
||||
'does not support reporting CPU traits.',
|
||||
{'virt_type': CONF.libvirt.virt_type})
|
||||
return
|
||||
|
||||
caps = deepcopy(self._host.get_capabilities())
|
||||
if cpu.mode in ('host-model', 'host-passthrough'):
|
||||
# Account for features in cpu_model_extra_flags conf
|
||||
host_features = [f.name for f in
|
||||
caps.host.cpu.features | cpu.features]
|
||||
return libvirt_utils.cpu_features_to_traits(host_features)
|
||||
|
||||
# Choose a default CPU model when cpu_mode is not specified
|
||||
if cpu.mode is None:
|
||||
caps.host.cpu.model = libvirt_utils.get_cpu_model_from_arch(
|
||||
caps.host.cpu.arch)
|
||||
caps.host.cpu.features = set()
|
||||
else:
|
||||
# For custom mode, set model to guest CPU model
|
||||
caps.host.cpu.model = cpu.model
|
||||
caps.host.cpu.features = set()
|
||||
# Account for features in cpu_model_extra_flags conf
|
||||
for f in cpu.features:
|
||||
caps.host.cpu.add_feature(
|
||||
vconfig.LibvirtConfigCPUFeature(name=f.name))
|
||||
|
||||
xml_str = caps.host.cpu.to_xml()
|
||||
LOG.info("Libvirt baseline CPU %s", xml_str)
|
||||
# TODO(lei-zh): baselineCPU is not supported on all platforms.
|
||||
# There is some work going on in the libvirt community to replace the
|
||||
# baseline call. Consider using the new apis when they are ready. See
|
||||
# https://www.redhat.com/archives/libvir-list/2018-May/msg01204.html.
|
||||
try:
|
||||
if hasattr(libvirt, 'VIR_CONNECT_BASELINE_CPU_EXPAND_FEATURES'):
|
||||
features = self._host.get_connection().baselineCPU(
|
||||
[xml_str],
|
||||
libvirt.VIR_CONNECT_BASELINE_CPU_EXPAND_FEATURES)
|
||||
else:
|
||||
features = self._host.get_connection().baselineCPU([xml_str])
|
||||
except libvirt.libvirtError as ex:
|
||||
with excutils.save_and_reraise_exception() as ctxt:
|
||||
error_code = ex.get_error_code()
|
||||
if error_code == libvirt.VIR_ERR_NO_SUPPORT:
|
||||
ctxt.reraise = False
|
||||
LOG.info('URI %(uri)s does not support full set'
|
||||
' of host capabilities: %(error)s',
|
||||
{'uri': self._host._uri, 'error': ex})
|
||||
return libvirt_utils.cpu_features_to_traits([])
|
||||
|
||||
cpu.parse_str(features)
|
||||
return libvirt_utils.cpu_features_to_traits(
|
||||
[f.name for f in cpu.features])
|
||||
|
@ -42,6 +42,43 @@ LOG = logging.getLogger(__name__)
|
||||
|
||||
RESIZE_SNAPSHOT_NAME = 'nova-resize'
|
||||
|
||||
# Mapping used to convert libvirt cpu features to traits, for more details, see
|
||||
# https://github.com/libvirt/libvirt/blob/master/src/cpu/cpu_map.xml.
|
||||
CPU_TRAITS_MAPPING = {
|
||||
'3dnow': 'HW_CPU_X86_3DNOW',
|
||||
'abm': 'HW_CPU_X86_ABM',
|
||||
'aes': 'HW_CPU_X86_AESNI',
|
||||
'avx': 'HW_CPU_X86_AVX',
|
||||
'avx2': 'HW_CPU_X86_AVX2',
|
||||
'avx512bw': 'HW_CPU_X86_AVX512BW',
|
||||
'avx512cd': 'HW_CPU_X86_AVX512CD',
|
||||
'avx512dq': 'HW_CPU_X86_AVX512DQ',
|
||||
'avx512er': 'HW_CPU_X86_AVX512ER',
|
||||
'avx512f': 'HW_CPU_X86_AVX512F',
|
||||
'avx512pf': 'HW_CPU_X86_AVX512PF',
|
||||
'avx512vl': 'HW_CPU_X86_AVX512VL',
|
||||
'bmi1': 'HW_CPU_X86_BMI',
|
||||
'bmi2': 'HW_CPU_X86_BMI2',
|
||||
'pclmuldq': 'HW_CPU_X86_CLMUL',
|
||||
'f16c': 'HW_CPU_X86_F16C',
|
||||
'fma': 'HW_CPU_X86_FMA3',
|
||||
'fma4': 'HW_CPU_X86_FMA4',
|
||||
'mmx': 'HW_CPU_X86_MMX',
|
||||
'mpx': 'HW_CPU_X86_MPX',
|
||||
'sha-ni': 'HW_CPU_X86_SHA',
|
||||
'sse': 'HW_CPU_X86_SSE',
|
||||
'sse2': 'HW_CPU_X86_SSE2',
|
||||
'sse3': 'HW_CPU_X86_SSE3',
|
||||
'sse4.1': 'HW_CPU_X86_SSE41',
|
||||
'sse4.2': 'HW_CPU_X86_SSE42',
|
||||
'sse4a': 'HW_CPU_X86_SSE4A',
|
||||
'ssse3': 'HW_CPU_X86_SSSE3',
|
||||
'svm': 'HW_CPU_X86_SVM',
|
||||
'tbm': 'HW_CPU_X86_TBM',
|
||||
'vmx': 'HW_CPU_X86_VMX',
|
||||
'xop': 'HW_CPU_X86_XOP'
|
||||
}
|
||||
|
||||
|
||||
def create_image(disk_format, path, size):
|
||||
"""Create a disk image
|
||||
@ -483,3 +520,25 @@ def is_valid_hostname(hostname):
|
||||
def version_to_string(version):
|
||||
"""Returns string version based on tuple"""
|
||||
return '.'.join([str(x) for x in version])
|
||||
|
||||
|
||||
def cpu_features_to_traits(features):
|
||||
"""Returns this driver's CPU traits dict where keys are trait names from
|
||||
CPU_TRAITS_MAPPING, values are boolean indicates whether the trait should
|
||||
be set in the provider tree.
|
||||
"""
|
||||
traits = {trait_name: False for trait_name in CPU_TRAITS_MAPPING.values()}
|
||||
for f in features:
|
||||
if f in CPU_TRAITS_MAPPING:
|
||||
traits[CPU_TRAITS_MAPPING[f]] = True
|
||||
|
||||
return traits
|
||||
|
||||
|
||||
def get_cpu_model_from_arch(arch):
|
||||
mode = 'qemu64'
|
||||
if arch == obj_fields.Architecture.I686:
|
||||
mode = 'qemu32'
|
||||
elif arch == obj_fields.Architecture.PPC64LE:
|
||||
mode = 'POWER8'
|
||||
return mode
|
||||
|
@ -0,0 +1,7 @@
|
||||
---
|
||||
|
||||
features:
|
||||
- Add support for reporting CPU traits to Placement
|
||||
in libvirt driver. For more detail, see
|
||||
https://specs.openstack.org/openstack/nova-specs/specs/rocky/approved/report-cpu-features-as-traits.html
|
||||
and https://docs.openstack.org/nova/latest/user/support-matrix.html.
|
Loading…
x
Reference in New Issue
Block a user