Skip to content

Commit 160d104

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "Provide HW_CPU_X86_AMD_SEV trait when SEV is supported"
2 parents f8969bc + f5964df commit 160d104

File tree

5 files changed

+290
-15
lines changed

5 files changed

+290
-15
lines changed

nova/tests/functional/libvirt/test_report_cpu_traits.py

Lines changed: 132 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,25 @@
1313
# License for the specific language governing permissions and limitations
1414
# under the License.
1515

16+
import mock
17+
18+
import os_traits as ost
19+
1620
from nova import conf
21+
from nova import test
1722
from nova.tests.functional.libvirt import integrated_helpers
23+
from nova.tests.unit.virt.libvirt import fakelibvirt
24+
from nova.virt.libvirt.host import SEV_KERNEL_PARAM_FILE
1825

1926
CONF = conf.CONF
2027

2128

22-
class LibvirtReportTraitsTests(
29+
class LibvirtReportTraitsTestBase(
2330
integrated_helpers.LibvirtProviderUsageBaseTestCase):
31+
pass
32+
33+
34+
class LibvirtReportTraitsTests(LibvirtReportTraitsTestBase):
2435
def test_report_cpu_traits(self):
2536
self.assertEqual([], self._get_all_providers())
2637
self.start_compute()
@@ -46,3 +57,123 @@ def test_report_cpu_traits(self):
4657
[u'HW_CPU_X86_VMX', u'HW_CPU_X86_AESNI', u'CUSTOM_TRAITS']
4758
)
4859
self.assertItemsEqual(expected_traits, traits)
60+
61+
62+
class LibvirtReportNoSevTraitsTests(LibvirtReportTraitsTestBase):
63+
STUB_INIT_HOST = False
64+
65+
@test.patch_exists(SEV_KERNEL_PARAM_FILE, False)
66+
def setUp(self):
67+
super(LibvirtReportNoSevTraitsTests, self).setUp()
68+
self.start_compute()
69+
70+
def test_sev_trait_off_on(self):
71+
"""Test that the compute service reports the SEV trait in the list of
72+
global traits, but doesn't immediately register it on the
73+
compute host resource provider in the placement API, due to
74+
the kvm-amd kernel module's sev parameter file being (mocked
75+
as) absent.
76+
77+
Then test that if the SEV capability appears (again via
78+
mocking), after a restart of the compute service, the trait
79+
gets registered on the compute host.
80+
"""
81+
self.assertFalse(self.compute.driver._host.supports_amd_sev)
82+
83+
sev_trait = ost.HW_CPU_X86_AMD_SEV
84+
85+
global_traits = self._get_all_traits()
86+
self.assertIn(sev_trait, global_traits)
87+
88+
traits = self._get_provider_traits(self.host_uuid)
89+
self.assertNotIn(sev_trait, traits)
90+
91+
# Now simulate the host gaining SEV functionality. Here we
92+
# simulate a kernel update or reconfiguration which causes the
93+
# kvm-amd kernel module's "sev" parameter to become available
94+
# and set to 1, however it could also happen via a libvirt
95+
# upgrade, for instance.
96+
sev_features = \
97+
fakelibvirt.virConnect._domain_capability_features_with_SEV
98+
with test.nested(
99+
self.patch_exists(SEV_KERNEL_PARAM_FILE, True),
100+
self.patch_open(SEV_KERNEL_PARAM_FILE, "1\n"),
101+
mock.patch.object(fakelibvirt.virConnect,
102+
'_domain_capability_features',
103+
new=sev_features)
104+
) as (mock_exists, mock_open, mock_features):
105+
# Retrigger the detection code. In the real world this
106+
# would be a restart of the compute service.
107+
self.compute.driver._host._set_amd_sev_support()
108+
self.assertTrue(self.compute.driver._host.supports_amd_sev)
109+
110+
mock_exists.assert_has_calls([mock.call(SEV_KERNEL_PARAM_FILE)])
111+
mock_open.assert_has_calls([mock.call(SEV_KERNEL_PARAM_FILE)])
112+
113+
# However it won't disappear in the provider tree and get synced
114+
# back to placement until we force a reinventory:
115+
self.compute.manager.reset()
116+
self._run_periodics()
117+
118+
traits = self._get_provider_traits(self.host_uuid)
119+
self.assertIn(sev_trait, traits)
120+
121+
# Sanity check that we've still got the trait globally.
122+
self.assertIn(sev_trait, self._get_all_traits())
123+
124+
125+
class LibvirtReportSevTraitsTests(LibvirtReportTraitsTestBase):
126+
STUB_INIT_HOST = False
127+
128+
@test.patch_exists(SEV_KERNEL_PARAM_FILE, True)
129+
@test.patch_open(SEV_KERNEL_PARAM_FILE, "1\n")
130+
@mock.patch.object(
131+
fakelibvirt.virConnect, '_domain_capability_features',
132+
new=fakelibvirt.virConnect._domain_capability_features_with_SEV)
133+
def setUp(self):
134+
super(LibvirtReportSevTraitsTests, self).setUp()
135+
self.start_compute()
136+
137+
def test_sev_trait_on_off(self):
138+
"""Test that the compute service reports the SEV trait in the list of
139+
global traits, and immediately registers it on the compute
140+
host resource provider in the placement API, due to the SEV
141+
capability being (mocked as) present.
142+
143+
Then test that if the SEV capability disappears (again via
144+
mocking), after a restart of the compute service, the trait
145+
gets removed from the compute host.
146+
"""
147+
self.assertTrue(self.compute.driver._host.supports_amd_sev)
148+
149+
sev_trait = ost.HW_CPU_X86_AMD_SEV
150+
151+
global_traits = self._get_all_traits()
152+
self.assertIn(sev_trait, global_traits)
153+
154+
traits = self._get_provider_traits(self.host_uuid)
155+
self.assertIn(sev_trait, traits)
156+
157+
# Now simulate the host losing SEV functionality. Here we
158+
# simulate a kernel downgrade or reconfiguration which causes
159+
# the kvm-amd kernel module's "sev" parameter to become
160+
# unavailable, however it could also happen via a libvirt
161+
# downgrade, for instance.
162+
with self.patch_exists(SEV_KERNEL_PARAM_FILE, False) as mock_exists:
163+
# Retrigger the detection code. In the real world this
164+
# would be a restart of the compute service.
165+
self.compute.driver._host._set_amd_sev_support()
166+
self.assertFalse(self.compute.driver._host.supports_amd_sev)
167+
168+
mock_exists.assert_has_calls([mock.call(SEV_KERNEL_PARAM_FILE)])
169+
170+
# However it won't disappear in the provider tree and get synced
171+
# back to placement until we force a reinventory:
172+
self.compute.manager.reset()
173+
self._run_periodics()
174+
175+
traits = self._get_provider_traits(self.host_uuid)
176+
self.assertNotIn(sev_trait, traits)
177+
178+
# Sanity check that we've still got the trait globally.
179+
self.assertIn(sev_trait, self._get_all_traits())

nova/tests/unit/virt/libvirt/test_driver.py

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
from os_brick import exception as brick_exception
4343
from os_brick.initiator import connector
4444
import os_resource_classes as orc
45+
import os_traits as ot
4546
import os_vif
4647
from oslo_concurrency import lockutils
4748
from oslo_concurrency import processutils
@@ -18617,7 +18618,8 @@ def _get_inventory(self):
1861718618
},
1861818619
}
1861918620

18620-
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver._get_cpu_traits',
18621+
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver.'
18622+
'_get_cpu_feature_traits',
1862118623
new=mock.Mock(return_value=cpu_traits))
1862218624
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver._get_gpu_inventories')
1862318625
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver._get_local_gb_info',
@@ -18751,7 +18753,8 @@ def test_update_provider_tree_with_cpu_traits(self):
1875118753
self.assertEqual(set(['HW_CPU_X86_AVX512F', 'HW_CPU_X86_BMI']),
1875218754
self.pt.data(self.cn_rp['uuid']).traits)
1875318755

18754-
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver._get_cpu_traits',
18756+
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver.'
18757+
'_get_cpu_feature_traits',
1875518758
new=mock.Mock(return_value=cpu_traits))
1875618759
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver.'
1875718760
'_get_mediated_device_information')
@@ -18883,7 +18886,8 @@ def test_update_provider_tree_for_vgpu_reshape(
1888318886
self.assertEqual(original_allocations[uuids.consumer2],
1888418887
allocations[uuids.consumer2])
1888518888

18886-
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver._get_cpu_traits',
18889+
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver.'
18890+
'_get_cpu_feature_traits',
1888718891
new=mock.Mock(return_value=cpu_traits))
1888818892
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver._get_gpu_inventories')
1888918893
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver._get_local_gb_info',
@@ -21678,6 +21682,10 @@ def test_recreate_mediated_device_on_init_host(
2167821682

2167921683
# Fake the fact that mdev1 is existing but mdev2 not
2168021684
def _exists(path):
21685+
# Keep the AMD SEV support check happy
21686+
if path == '/sys/module/kvm_amd/parameters/sev':
21687+
return False
21688+
2168121689
# Just verify what we ask
2168221690
self.assertIn('/sys/bus/mdev/devices/', path)
2168321691
return True if uuids.mdev1 in path else False
@@ -21750,13 +21758,21 @@ def test_detach_mediated_devices_raises_exc(self):
2175021758
self.assertRaises(test.TestingException,
2175121759
self._test_detach_mediated_devices, exc)
2175221760

21761+
@mock.patch.object(libvirt_driver.LibvirtDriver, '_get_cpu_feature_traits',
21762+
new=mock.Mock(return_value=None))
21763+
def test_cpu_traits_sev_no_feature_traits(self):
21764+
for support in (False, True):
21765+
self.drvr._host._supports_amd_sev = support
21766+
self.assertEqual({ot.HW_CPU_X86_AMD_SEV: support},
21767+
self.drvr._get_cpu_traits())
21768+
2175321769
def test_cpu_traits_with_passthrough_mode(self):
2175421770
"""Test getting CPU traits when cpu_mmode is 'host-passthrough', traits
2175521771
are calculated from fakelibvirt's baseline CPU features.
2175621772
"""
2175721773
self.flags(cpu_mode='host-passthrough', group='libvirt')
2175821774
self.assertTraitsEqual(['HW_CPU_X86_AESNI', 'HW_CPU_X86_VMX'],
21759-
self.drvr._get_cpu_traits())
21775+
self.drvr._get_cpu_feature_traits())
2176021776

2176121777
@mock.patch('nova.virt.libvirt.host.libvirt.Connection.baselineCPU')
2176221778
def test_cpu_traits_with_mode_none(self, mock_baseline):
@@ -21767,7 +21783,7 @@ def test_cpu_traits_with_mode_none(self, mock_baseline):
2176721783
mock_baseline.return_value = _fake_qemu64_cpu_feature
2176821784
self.assertTraitsEqual(['HW_CPU_X86_SSE', 'HW_CPU_X86_SVM',
2176921785
'HW_CPU_X86_MMX', 'HW_CPU_X86_SSE2'],
21770-
self.drvr._get_cpu_traits())
21786+
self.drvr._get_cpu_feature_traits())
2177121787

2177221788
mock_baseline.assert_called_with([u'''<cpu>
2177321789
<arch>x86_64</arch>
@@ -21803,7 +21819,7 @@ def test_cpu_traits_with_mode_custom(self, mock_baseline):
2180321819
'HW_CPU_X86_SSE2',
2180421820
'HW_CPU_X86_SSE',
2180521821
'HW_CPU_X86_MMX'
21806-
], self.drvr._get_cpu_traits()
21822+
], self.drvr._get_cpu_feature_traits()
2180721823
)
2180821824
mock_baseline.assert_called_with([u'''<cpu>
2180921825
<arch>x86_64</arch>
@@ -21822,7 +21838,7 @@ def test_cpu_traits_with_no_baseline_support(self, mock_baseline):
2182221838
'this function is not supported by the connection driver',
2182321839
error_code=fakelibvirt.VIR_ERR_NO_SUPPORT)
2182421840
mock_baseline.side_effect = not_supported_exc
21825-
self.assertTraitsEqual([], self.drvr._get_cpu_traits())
21841+
self.assertTraitsEqual([], self.drvr._get_cpu_feature_traits())
2182621842

2182721843
@mock.patch('nova.virt.libvirt.host.libvirt.Connection.getCapabilities')
2182821844
@mock.patch('nova.virt.libvirt.host.libvirt.Connection.baselineCPU')
@@ -21865,7 +21881,7 @@ def mocked_baseline(cpu_xml, *args):
2186521881
raise missing_model_exc
2186621882
mock_baseline.side_effect = mocked_baseline
2186721883

21868-
self.assertTraitsEqual([], self.drvr._get_cpu_traits())
21884+
self.assertTraitsEqual([], self.drvr._get_cpu_feature_traits())
2186921885

2187021886
def test_cpu_traits_with_invalid_virt_type(self):
2187121887
"""Test getting CPU traits when using a virt_type that doesn't support
@@ -21876,7 +21892,7 @@ def test_cpu_traits_with_invalid_virt_type(self):
2187621892
virt_type='lxc',
2187721893
group='libvirt'
2187821894
)
21879-
self.assertRaises(exception.Invalid, self.drvr._get_cpu_traits)
21895+
self.assertRaises(exception.Invalid, self.drvr._get_cpu_feature_traits)
2188021896

2188121897
@mock.patch('nova.virt.libvirt.host.libvirt.Connection.getCapabilities')
2188221898
@mock.patch('nova.virt.libvirt.utils.cpu_features_to_traits')
@@ -21902,7 +21918,7 @@ def test_cpu_traits_with_mode_passthrough_and_extra_flags(
2190221918
</host>
2190321919
</capabilities>
2190421920
"""
21905-
self.drvr._get_cpu_traits()
21921+
self.drvr._get_cpu_feature_traits()
2190621922
self.assertItemsEqual(['pcid', 'erms'], mock_to_traits.call_args[0][0])
2190721923

2190821924
@mock.patch('nova.virt.libvirt.host.libvirt.Connection.baselineCPU')
@@ -21924,7 +21940,7 @@ def test_cpu_traits_with_mode_custom_and_extra_flags(self, mock_to_traits,
2192421940
<feature policy='require' name='pcid'/>
2192521941
</cpu>
2192621942
"""
21927-
self.drvr._get_cpu_traits()
21943+
self.drvr._get_cpu_feature_traits()
2192821944
mock_baseline.assert_called_with([u'''<cpu>
2192921945
<arch>x86_64</arch>
2193021946
<model>IvyBridge</model>
@@ -21952,7 +21968,7 @@ def test_cpu_traits_with_mode_not_set_and_extra_flags(self, mock_to_traits,
2195221968
<feature policy='require' name='erms'/>
2195321969
</cpu>
2195421970
"""
21955-
self.drvr._get_cpu_traits()
21971+
self.drvr._get_cpu_feature_traits()
2195621972
self.assertItemsEqual(['pcid', 'erms'], mock_to_traits.call_args[0][0])
2195721973

2195821974
def test_cpu_traits_with_mode_none_and_invalid_virt_type(self):
@@ -21962,7 +21978,7 @@ def test_cpu_traits_with_mode_none_and_invalid_virt_type(self):
2196221978
self.flags(cpu_mode='none',
2196321979
virt_type='lxc',
2196421980
group='libvirt')
21965-
self.assertIsNone(self.drvr._get_cpu_traits())
21981+
self.assertIsNone(self.drvr._get_cpu_feature_traits())
2196621982

2196721983
@mock.patch('nova.virt.libvirt.host.libvirt.Connection.getCapabilities')
2196821984
@mock.patch('nova.virt.libvirt.host.libvirt.Connection.baselineCPU')
@@ -21989,7 +22005,7 @@ def test_cpu_traits_with_mode_none_on_power(self, mock_baseline, mock_cap):
2198922005
<vendor>IBM</vendor>
2199022006
</cpu>
2199122007
'''
21992-
self.drvr._get_cpu_traits()
22008+
self.drvr._get_cpu_feature_traits()
2199322009
mock_baseline.assert_called_with([u'''<cpu>
2199422010
<arch>ppc64le</arch>
2199522011
<model>POWER8</model>

nova/tests/unit/virt/libvirt/test_host.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,15 @@
1414
# License for the specific language governing permissions and limitations
1515
# under the License.
1616

17+
import os
18+
1719
import eventlet
1820
from eventlet import greenthread
1921
import mock
2022
from oslo_utils.fixture import uuidsentinel as uuids
2123
from oslo_utils import uuidutils
2224
import six
25+
from six.moves import builtins
2326
import testtools
2427

2528

@@ -1163,3 +1166,61 @@ def test_is_cpu_control_policy_capable_ko(self):
11631166
@mock.patch('six.moves.builtins.open', side_effect=IOError)
11641167
def test_is_cpu_control_policy_capable_ioerror(self, mock_open):
11651168
self.assertFalse(self.host.is_cpu_control_policy_capable())
1169+
1170+
1171+
vc = fakelibvirt.virConnect
1172+
1173+
1174+
class TestLibvirtSEV(test.NoDBTestCase):
1175+
"""Libvirt host tests for AMD SEV support."""
1176+
1177+
def setUp(self):
1178+
super(TestLibvirtSEV, self).setUp()
1179+
1180+
self.useFixture(fakelibvirt.FakeLibvirtFixture())
1181+
self.host = host.Host("qemu:///system")
1182+
1183+
1184+
class TestLibvirtSEVUnsupported(TestLibvirtSEV):
1185+
@mock.patch.object(os.path, 'exists', return_value=False)
1186+
def test_kernel_parameter_missing(self, fake_exists):
1187+
self.assertFalse(self.host._kernel_supports_amd_sev())
1188+
fake_exists.assert_called_once_with(
1189+
'/sys/module/kvm_amd/parameters/sev')
1190+
1191+
@mock.patch.object(os.path, 'exists', return_value=True)
1192+
@mock.patch.object(builtins, 'open', mock.mock_open(read_data="0\n"))
1193+
def test_kernel_parameter_zero(self, fake_exists):
1194+
self.assertFalse(self.host._kernel_supports_amd_sev())
1195+
fake_exists.assert_called_once_with(
1196+
'/sys/module/kvm_amd/parameters/sev')
1197+
1198+
@mock.patch.object(os.path, 'exists', return_value=True)
1199+
@mock.patch.object(builtins, 'open', mock.mock_open(read_data="1\n"))
1200+
def test_kernel_parameter_one(self, fake_exists):
1201+
self.assertTrue(self.host._kernel_supports_amd_sev())
1202+
fake_exists.assert_called_once_with(
1203+
'/sys/module/kvm_amd/parameters/sev')
1204+
1205+
@mock.patch.object(os.path, 'exists', return_value=True)
1206+
@mock.patch.object(builtins, 'open', mock.mock_open(read_data="1\n"))
1207+
def test_unsupported_without_feature(self, fake_exists):
1208+
self.assertFalse(self.host.supports_amd_sev)
1209+
1210+
@mock.patch.object(os.path, 'exists', return_value=True)
1211+
@mock.patch.object(builtins, 'open', mock.mock_open(read_data="1\n"))
1212+
@mock.patch.object(vc, '_domain_capability_features',
1213+
new=vc._domain_capability_features_with_SEV_unsupported)
1214+
def test_unsupported_with_feature(self, fake_exists):
1215+
self.assertFalse(self.host.supports_amd_sev)
1216+
1217+
1218+
class TestLibvirtSEVSupported(TestLibvirtSEV):
1219+
"""Libvirt driver tests for when AMD SEV support is present."""
1220+
1221+
@mock.patch.object(os.path, 'exists', return_value=True)
1222+
@mock.patch.object(builtins, 'open', mock.mock_open(read_data="1\n"))
1223+
@mock.patch.object(vc, '_domain_capability_features',
1224+
new=vc._domain_capability_features_with_SEV)
1225+
def test_supported_with_feature(self, fake_exists):
1226+
self.assertTrue(self.host.supports_amd_sev)

0 commit comments

Comments
 (0)