Skip to content

Commit d066dd3

Browse files
Serban Iorgaacatangiu
authored andcommitted
add cpuid integration tests
Signed-off-by: Serban Iorga <[email protected]>
1 parent 8e4c363 commit d066dd3

File tree

2 files changed

+199
-63
lines changed

2 files changed

+199
-63
lines changed

tests/integration_tests/build/test_coverage.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
import host_tools.cargo_build as host # pylint: disable=import-error
2020

21-
COVERAGE_TARGET_PCT = 83.9
21+
COVERAGE_TARGET_PCT = 84.1
2222
COVERAGE_MAX_DELTA = 0.01
2323

2424
CARGO_KCOV_REL_PATH = os.path.join(host.CARGO_BUILD_REL_PATH, 'kcov')

tests/integration_tests/functional/test_cpu_features.py

Lines changed: 198 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -4,78 +4,216 @@
44

55
import re
66

7+
from enum import Enum, auto
8+
79
import host_tools.network as net_tools # pylint: disable=import-error
810

911

10-
def test_1vcpu(test_microvm_with_ssh, network_config):
11-
"""Test CPU feature emulation with 1 vCPU."""
12-
test_microvm = test_microvm_with_ssh
13-
test_microvm.spawn()
12+
class CpuVendor(Enum):
13+
"""CPU vendors enum."""
1414

15-
# Set up the microVM with 1 vCPUs, 256 MiB of RAM, no network ifaces, and
16-
# a root file system with the rw permission. The network interfaces is
17-
# added after we get a unique MAC and IP.
18-
test_microvm.basic_config(vcpu_count=1)
15+
AMD = auto()
16+
INTEL = auto()
1917

20-
_tap, _, _ = test_microvm.ssh_network_config(network_config, '1')
21-
test_microvm.start()
18+
19+
def _get_cpu_vendor():
20+
cif = open('/proc/cpuinfo', 'r')
21+
host_vendor_id = None
22+
while True:
23+
line = cif.readline()
24+
if line == '':
25+
break
26+
mo = re.search("^vendor_id\\s+:\\s+(.+)$", line)
27+
if mo:
28+
host_vendor_id = mo.group(1)
29+
cif.close()
30+
assert host_vendor_id is not None
31+
32+
if host_vendor_id == "AuthenticAMD":
33+
return CpuVendor.AMD
34+
return CpuVendor.INTEL
35+
36+
37+
def _check_guest_cmd_output(test_microvm, guest_cmd, expected_header,
38+
expected_separator,
39+
expected_key_value_store):
40+
ssh_connection = net_tools.SSHConnection(test_microvm.ssh_config)
41+
_, stdout, stderr = ssh_connection.execute_command(guest_cmd)
42+
43+
assert stderr.read().decode("utf-8") == ''
44+
while True:
45+
line = stdout.readline().decode('utf-8')
46+
if line != '':
47+
# All the keys have been matched. Stop.
48+
if not expected_key_value_store:
49+
break
50+
51+
# Try to match the header if needed.
52+
if expected_header not in (None, ''):
53+
if line.strip() == expected_header:
54+
expected_header = None
55+
continue
56+
else:
57+
continue
58+
59+
# See if any key matches.
60+
# We Use a try-catch block here since line.split() may fail.
61+
try:
62+
[key, value] = list(
63+
map(lambda x: x.strip(), line.split(expected_separator)))
64+
except ValueError:
65+
continue
66+
67+
if key in expected_key_value_store.keys():
68+
assert value == expected_key_value_store[key], \
69+
"%s does not have the expected value" % key
70+
del expected_key_value_store[key]
71+
72+
else:
73+
break
74+
75+
assert not expected_key_value_store, \
76+
"some keys in dictionary have not been found in the output: %s" \
77+
% expected_key_value_store
78+
79+
80+
def _check_cpu_topology(test_microvm, expected_cpu_count,
81+
expected_threads_per_core,
82+
expected_cpus_list):
2283
expected_cpu_topology = {
23-
"CPU(s)": "1",
24-
"On-line CPU(s) list": "0",
25-
"Thread(s) per core": "1",
26-
"Core(s) per socket": "1",
84+
"CPU(s)": str(expected_cpu_count),
85+
"On-line CPU(s) list": expected_cpus_list,
86+
"Thread(s) per core": str(expected_threads_per_core),
87+
"Core(s) per socket": str(
88+
int(expected_cpu_count / expected_threads_per_core)),
2789
"Socket(s)": "1",
2890
"NUMA node(s)": "1"
2991
}
30-
_check_cpu_topology(test_microvm, expected_cpu_topology)
92+
93+
_check_guest_cmd_output(test_microvm, "lscpu", None, ':',
94+
expected_cpu_topology)
95+
96+
97+
def _check_cpu_features(test_microvm, expected_cpu_count, expected_htt):
98+
expected_cpu_features = {
99+
"cpu count": '{} ({})'.format(hex(expected_cpu_count),
100+
expected_cpu_count),
101+
"CLFLUSH line size": "0x8 (8)",
102+
"hypervisor guest status": "true",
103+
"hyper-threading / multi-core supported": expected_htt
104+
}
105+
106+
_check_guest_cmd_output(test_microvm, "cpuid -1", None, '=',
107+
expected_cpu_features)
108+
109+
110+
def _check_cache_topology(test_microvm, num_vcpus_on_lvl_1_cache,
111+
num_vcpus_on_lvl_3_cache):
112+
expected_lvl_1_str = '{} ({})'.format(hex(num_vcpus_on_lvl_1_cache),
113+
num_vcpus_on_lvl_1_cache)
114+
expected_lvl_3_str = '{} ({})'.format(hex(num_vcpus_on_lvl_3_cache),
115+
num_vcpus_on_lvl_3_cache)
116+
117+
cpu_vendor = _get_cpu_vendor()
118+
if cpu_vendor == CpuVendor.AMD:
119+
expected_level_1_topology = {
120+
"level": '0x1 (1)',
121+
"extra cores sharing this cache": expected_lvl_1_str
122+
}
123+
expected_level_3_topology = {
124+
"level": '0x3 (3)',
125+
"extra cores sharing this cache": expected_lvl_3_str
126+
}
127+
elif cpu_vendor == CpuVendor.INTEL:
128+
expected_level_1_topology = {
129+
"cache level": '0x1 (1)',
130+
"extra threads sharing this cache": expected_lvl_1_str,
131+
}
132+
expected_level_3_topology = {
133+
"cache level": '0x3 (3)',
134+
"extra threads sharing this cache": expected_lvl_3_str,
135+
}
136+
137+
_check_guest_cmd_output(test_microvm, "cpuid -1", "--- cache 0 ---", '=',
138+
expected_level_1_topology)
139+
_check_guest_cmd_output(test_microvm, "cpuid -1", "--- cache 1 ---", '=',
140+
expected_level_1_topology)
141+
_check_guest_cmd_output(test_microvm, "cpuid -1", "--- cache 2 ---", '=',
142+
expected_level_1_topology)
143+
_check_guest_cmd_output(test_microvm, "cpuid -1", "--- cache 3 ---", '=',
144+
expected_level_3_topology)
145+
146+
147+
def test_1vcpu_ht_disabled(test_microvm_with_ssh, network_config):
148+
"""Check the CPUID for a microvm with the specified config."""
149+
test_microvm_with_ssh.spawn()
150+
test_microvm_with_ssh.basic_config(vcpu_count=1, ht_enabled=False)
151+
_tap, _, _ = test_microvm_with_ssh.ssh_network_config(network_config, '1')
152+
test_microvm_with_ssh.start()
153+
154+
_check_cpu_topology(test_microvm_with_ssh, 1, 1, "0")
155+
_check_cpu_features(test_microvm_with_ssh, 1, "false")
156+
_check_cache_topology(test_microvm_with_ssh, 0, 0)
157+
158+
159+
def test_1vcpu_ht_enabled(test_microvm_with_ssh, network_config):
160+
"""Check the CPUID for a microvm with the specified config."""
161+
test_microvm_with_ssh.spawn()
162+
test_microvm_with_ssh.basic_config(vcpu_count=1, ht_enabled=True)
163+
_tap, _, _ = test_microvm_with_ssh.ssh_network_config(network_config, '1')
164+
test_microvm_with_ssh.start()
165+
166+
_check_cpu_topology(test_microvm_with_ssh, 1, 1, "0")
167+
_check_cpu_features(test_microvm_with_ssh, 1, "false")
168+
_check_cache_topology(test_microvm_with_ssh, 0, 0)
31169

32170

33171
def test_2vcpu_ht_disabled(test_microvm_with_ssh, network_config):
34-
"""Test CPU feature emulation with 2 vCPUs, and no hyperthreading."""
35-
test_microvm = test_microvm_with_ssh
36-
test_microvm.spawn()
172+
"""Check the CPUID for a microvm with the specified config."""
173+
test_microvm_with_ssh.spawn()
174+
test_microvm_with_ssh.basic_config(vcpu_count=2, ht_enabled=False)
175+
_tap, _, _ = test_microvm_with_ssh.ssh_network_config(network_config, '1')
176+
test_microvm_with_ssh.start()
37177

38-
# Set up the microVM with 2 vCPUs, 256 MiB of RAM, 0 network ifaces, and
39-
# a root file system with the rw permission. The network interfaces is
40-
# added after we get a unique MAC and IP.
41-
test_microvm.basic_config(vcpu_count=2, ht_enabled=False)
178+
_check_cpu_topology(test_microvm_with_ssh, 2, 1, "0,1")
179+
_check_cpu_features(test_microvm_with_ssh, 2, "true")
180+
_check_cache_topology(test_microvm_with_ssh, 0, 1)
42181

43-
_tap, _, _ = test_microvm.ssh_network_config(network_config, '1')
44182

45-
test_microvm.start()
183+
def test_2vcpu_ht_enabled(test_microvm_with_ssh, network_config):
184+
"""Check the CPUID for a microvm with the specified config."""
185+
test_microvm_with_ssh.spawn()
186+
test_microvm_with_ssh.basic_config(vcpu_count=2, ht_enabled=True)
187+
_tap, _, _ = test_microvm_with_ssh.ssh_network_config(network_config, '1')
188+
test_microvm_with_ssh.start()
46189

47-
expected_cpu_topology = {
48-
"CPU(s)": "2",
49-
"On-line CPU(s) list": "0,1",
50-
"Thread(s) per core": "1",
51-
"Core(s) per socket": "2",
52-
"Socket(s)": "1",
53-
"NUMA node(s)": "1"
54-
}
55-
_check_cpu_topology(test_microvm, expected_cpu_topology)
190+
_check_cpu_topology(test_microvm_with_ssh, 2, 2, "0,1")
191+
_check_cpu_features(test_microvm_with_ssh, 2, "true")
192+
_check_cache_topology(test_microvm_with_ssh, 1, 1)
56193

57194

58-
def _check_cpu_topology(test_microvm, expected_cpu_topology):
59-
"""Perform common microvm setup for different CPU topology tests.
195+
def test_16vcpu_ht_disabled(test_microvm_with_ssh, network_config):
196+
"""Check the CPUID for a microvm with the specified config."""
197+
test_microvm_with_ssh.spawn()
198+
test_microvm_with_ssh.basic_config(vcpu_count=16, ht_enabled=False)
199+
_tap, _, _ = test_microvm_with_ssh.ssh_network_config(network_config, '1')
200+
test_microvm_with_ssh.start()
60201

61-
This is a wrapper function for calling lscpu and checking if the
62-
command returns the expected cpu topology.
63-
"""
64-
ssh_connection = net_tools.SSHConnection(test_microvm.ssh_config)
202+
_check_cpu_topology(test_microvm_with_ssh, 16, 1, "0-15")
203+
_check_cpu_features(test_microvm_with_ssh, 16, "true")
204+
_check_cache_topology(test_microvm_with_ssh, 0, 15)
65205

66-
# Execute the lscpu command to check the guest topology
67-
_, stdout, stderr = ssh_connection.execute_command("lscpu")
68-
assert stderr.read().decode("utf-8") == ''
69-
# Read the stdout of lscpu line by line to check the relevant information.
70-
while True:
71-
line = stdout.readline().decode('utf-8')
72-
if line != '':
73-
[key, value] = list(map(lambda x: x.strip(), line.split(':')))
74-
if key in expected_cpu_topology.keys():
75-
assert value == expected_cpu_topology[key],\
76-
"%s does not have the expected value" % key
77-
else:
78-
break
206+
207+
def test_16vcpu_ht_enabled(test_microvm_with_ssh, network_config):
208+
"""Check the CPUID for a microvm with the specified config."""
209+
test_microvm_with_ssh.spawn()
210+
test_microvm_with_ssh.basic_config(vcpu_count=16, ht_enabled=True)
211+
_tap, _, _ = test_microvm_with_ssh.ssh_network_config(network_config, '1')
212+
test_microvm_with_ssh.start()
213+
214+
_check_cpu_topology(test_microvm_with_ssh, 16, 2, "0-15")
215+
_check_cpu_features(test_microvm_with_ssh, 16, "true")
216+
_check_cache_topology(test_microvm_with_ssh, 1, 15)
79217

80218

81219
def test_brand_string(test_microvm_with_ssh, network_config):
@@ -85,24 +223,21 @@ def test_brand_string(test_microvm_with_ssh, network_config):
85223
Intel(R) Xeon(R) Processor @ {host frequency}
86224
where {host frequency} is the frequency reported by the host CPUID
87225
(e.g. 4.01GHz)
88-
* For non-Intel CPUs, the guest brand string should be:
89-
Intel(R) Xeon(R) Processor
226+
* For AMD CPUs, the guest brand string should be:
227+
AMD EPYC
228+
* For other CPUs, the guest brand string should be:
229+
""
90230
"""
91231
cif = open('/proc/cpuinfo', 'r')
92232
host_brand_string = None
93-
host_vendor_id = None
94233
while True:
95234
line = cif.readline()
96235
if line == '':
97236
break
98-
mo = re.search("^vendor_id\\s+:\\s+(.+)$", line)
99-
if mo:
100-
host_vendor_id = mo.group(1)
101237
mo = re.search("^model name\\s+:\\s+(.+)$", line)
102238
if mo:
103239
host_brand_string = mo.group(1)
104240
cif.close()
105-
assert host_vendor_id is not None
106241
assert host_brand_string is not None
107242

108243
test_microvm = test_microvm_with_ssh
@@ -124,13 +259,14 @@ def test_brand_string(test_microvm_with_ssh, network_config):
124259
guest_brand_string = mo.group(1)
125260
assert guest_brand_string
126261

262+
cpu_vendor = _get_cpu_vendor()
127263
expected_guest_brand_string = ""
128-
if host_vendor_id == "GenuineIntel":
264+
if cpu_vendor == CpuVendor.AMD:
265+
expected_guest_brand_string += "AMD EPYC"
266+
elif cpu_vendor == CpuVendor.INTEL:
129267
expected_guest_brand_string = "Intel(R) Xeon(R) Processor"
130268
mo = re.search("[.0-9]+[MG]Hz", host_brand_string)
131269
if mo:
132270
expected_guest_brand_string += " @ " + mo.group(0)
133-
elif host_vendor_id == "AuthenticAMD":
134-
expected_guest_brand_string += "AMD EPYC"
135271

136272
assert guest_brand_string == expected_guest_brand_string

0 commit comments

Comments
 (0)