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:
leizhang 2018-04-11 16:30:01 +08:00 committed by Matt Riedemann
parent 4aad363fe4
commit 7637026b90
13 changed files with 631 additions and 3 deletions

@ -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,

@ -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.