master
py 415 lines 15.6 KB
Raw
1 #!/usr/bin/env python3
2 #
3 # Functional test that boots a Linux kernel and checks the console
4 #
5 # Copyright IBM Corp. 2023
6 #
7 # Author:
8 # Pierre Morel <pmorel@linux.ibm.com>
9 #
10 # This work is licensed under the terms of the GNU GPL, version 2 or
11 # later. See the COPYING file in the top-level directory.
12
13 from qemu_test import QemuSystemTest, Asset
14 from qemu_test import exec_command
15 from qemu_test import exec_command_and_wait_for_pattern
16 from qemu_test import wait_for_console_pattern
17
18
19 class S390CPUTopology(QemuSystemTest):
20 """
21 S390x CPU topology consists of 4 topology layers, from bottom to top,
22 the cores, sockets, books and drawers and 2 modifiers attributes,
23 the entitlement and the dedication.
24 See: docs/system/s390x/cpu-topology.rst.
25
26 S390x CPU topology is setup in different ways:
27 - implicitly from the '-smp' argument by completing each topology
28 level one after the other beginning with drawer 0, book 0 and
29 socket 0.
30 - explicitly from the '-device' argument on the QEMU command line
31 - explicitly by hotplug of a new CPU using QMP or HMP
32 - it is modified by using QMP 'set-cpu-topology'
33
34 The S390x modifier attribute entitlement depends on the machine
35 polarization, which can be horizontal or vertical.
36 The polarization is changed on a request from the guest.
37 """
38 timeout = 90
39 event_timeout = 10
40
41 KERNEL_COMMON_COMMAND_LINE = ('printk.time=0 '
42 'root=/dev/ram '
43 'selinux=0 '
44 'rdinit=/bin/sh')
45 ASSET_F35_KERNEL = Asset(
46 ('https://archives.fedoraproject.org/pub/archive'
47 '/fedora-secondary/releases/35/Server/s390x/os'
48 '/images/kernel.img'),
49 '1f2dddfd11bb1393dd2eb2e784036fbf6fc11057a6d7d27f9eb12d3edc67ef73')
50
51 ASSET_F35_INITRD = Asset(
52 ('https://archives.fedoraproject.org/pub/archive'
53 '/fedora-secondary/releases/35/Server/s390x/os'
54 '/images/initrd.img'),
55 '1100145fbca00240c8c372ae4b89b48c99844bc189b3dfbc3f481dc60055ca46')
56
57 def wait_until_booted(self):
58 wait_for_console_pattern(self, 'no job control',
59 failure_message='Kernel panic - not syncing',
60 vm=None)
61
62 def check_topology(self, c, s, b, d, e, t):
63 res = self.vm.qmp('query-cpus-fast')
64 cpus = res['return']
65 for cpu in cpus:
66 core = cpu['props']['core-id']
67 socket = cpu['props']['socket-id']
68 book = cpu['props']['book-id']
69 drawer = cpu['props']['drawer-id']
70 entitlement = cpu.get('entitlement')
71 dedicated = cpu.get('dedicated')
72 if core == c:
73 self.assertEqual(drawer, d)
74 self.assertEqual(book, b)
75 self.assertEqual(socket, s)
76 self.assertEqual(entitlement, e)
77 self.assertEqual(dedicated, t)
78
79 def kernel_init(self):
80 """
81 We need a VM that supports CPU topology,
82 currently this only the case when using KVM, not TCG.
83 We need a kernel supporting the CPU topology.
84 We need a minimal root filesystem with a shell.
85 """
86 self.require_accelerator("kvm")
87 kernel_path = self.ASSET_F35_KERNEL.fetch()
88 initrd_path = self.uncompress(self.ASSET_F35_INITRD, format="xz")
89
90 self.vm.set_console()
91 kernel_command_line = self.KERNEL_COMMON_COMMAND_LINE
92 self.vm.add_args('-nographic',
93 '-enable-kvm',
94 '-cpu', 'max,ctop=on',
95 '-m', '512',
96 '-kernel', kernel_path,
97 '-initrd', initrd_path,
98 '-append', kernel_command_line)
99
100 def system_init(self):
101 self.log.info("System init")
102 exec_command_and_wait_for_pattern(self,
103 """ mount proc -t proc /proc;
104 mount sys -t sysfs /sys;
105 cat /sys/devices/system/cpu/dispatching """,
106 '0')
107
108 def test_single(self):
109 """
110 This test checks the simplest topology with a single CPU.
111 """
112 self.set_machine('s390-ccw-virtio')
113 self.kernel_init()
114 self.vm.launch()
115 self.wait_until_booted()
116 self.check_topology(0, 0, 0, 0, 'medium', False)
117
118 def test_default(self):
119 """
120 This test checks the implicit topology.
121 """
122 self.set_machine('s390-ccw-virtio')
123 self.kernel_init()
124 self.vm.add_args('-smp',
125 '13,drawers=2,books=2,sockets=3,cores=2,maxcpus=24')
126 self.vm.launch()
127 self.wait_until_booted()
128 self.check_topology(0, 0, 0, 0, 'medium', False)
129 self.check_topology(1, 0, 0, 0, 'medium', False)
130 self.check_topology(2, 1, 0, 0, 'medium', False)
131 self.check_topology(3, 1, 0, 0, 'medium', False)
132 self.check_topology(4, 2, 0, 0, 'medium', False)
133 self.check_topology(5, 2, 0, 0, 'medium', False)
134 self.check_topology(6, 0, 1, 0, 'medium', False)
135 self.check_topology(7, 0, 1, 0, 'medium', False)
136 self.check_topology(8, 1, 1, 0, 'medium', False)
137 self.check_topology(9, 1, 1, 0, 'medium', False)
138 self.check_topology(10, 2, 1, 0, 'medium', False)
139 self.check_topology(11, 2, 1, 0, 'medium', False)
140 self.check_topology(12, 0, 0, 1, 'medium', False)
141
142 def test_move(self):
143 """
144 This test checks the topology modification by moving a CPU
145 to another socket: CPU 0 is moved from socket 0 to socket 2.
146 """
147 self.set_machine('s390-ccw-virtio')
148 self.kernel_init()
149 self.vm.add_args('-smp',
150 '1,drawers=2,books=2,sockets=3,cores=2,maxcpus=24')
151 self.vm.launch()
152 self.wait_until_booted()
153
154 self.check_topology(0, 0, 0, 0, 'medium', False)
155 res = self.vm.qmp('set-cpu-topology',
156 {'core-id': 0, 'socket-id': 2, 'entitlement': 'low'})
157 self.assertEqual(res['return'], {})
158 self.check_topology(0, 2, 0, 0, 'low', False)
159
160 def test_dash_device(self):
161 """
162 This test verifies that a CPU defined with the '-device'
163 command line option finds its right place inside the topology.
164 """
165 self.set_machine('s390-ccw-virtio')
166 self.kernel_init()
167 self.vm.add_args('-smp',
168 '1,drawers=2,books=2,sockets=3,cores=2,maxcpus=24')
169 self.vm.add_args('-device', 'max-s390x-cpu,core-id=10')
170 self.vm.add_args('-device',
171 'max-s390x-cpu,'
172 'core-id=1,socket-id=0,book-id=1,drawer-id=1,entitlement=low')
173 self.vm.add_args('-device',
174 'max-s390x-cpu,'
175 'core-id=2,socket-id=0,book-id=1,drawer-id=1,entitlement=medium')
176 self.vm.add_args('-device',
177 'max-s390x-cpu,'
178 'core-id=3,socket-id=1,book-id=1,drawer-id=1,entitlement=high')
179 self.vm.add_args('-device',
180 'max-s390x-cpu,'
181 'core-id=4,socket-id=1,book-id=1,drawer-id=1')
182 self.vm.add_args('-device',
183 'max-s390x-cpu,'
184 'core-id=5,socket-id=2,book-id=1,drawer-id=1,dedicated=true')
185
186 self.vm.launch()
187 self.wait_until_booted()
188
189 self.check_topology(10, 2, 1, 0, 'medium', False)
190 self.check_topology(1, 0, 1, 1, 'low', False)
191 self.check_topology(2, 0, 1, 1, 'medium', False)
192 self.check_topology(3, 1, 1, 1, 'high', False)
193 self.check_topology(4, 1, 1, 1, 'medium', False)
194 self.check_topology(5, 2, 1, 1, 'high', True)
195
196
197 def guest_set_dispatching(self, dispatching):
198 exec_command(self,
199 f'echo {dispatching} > /sys/devices/system/cpu/dispatching')
200 self.vm.event_wait('CPU_POLARIZATION_CHANGE', self.event_timeout)
201 exec_command_and_wait_for_pattern(self,
202 'cat /sys/devices/system/cpu/dispatching', dispatching)
203
204
205 def test_polarization(self):
206 """
207 This test verifies that QEMU modifies the entitlement change after
208 several guest polarization change requests.
209 """
210 self.set_machine('s390-ccw-virtio')
211 self.kernel_init()
212 self.vm.launch()
213 self.wait_until_booted()
214
215 self.system_init()
216 res = self.vm.qmp('query-s390x-cpu-polarization')
217 self.assertEqual(res['return']['polarization'], 'horizontal')
218 self.check_topology(0, 0, 0, 0, 'medium', False)
219
220 self.guest_set_dispatching('1')
221 res = self.vm.qmp('query-s390x-cpu-polarization')
222 self.assertEqual(res['return']['polarization'], 'vertical')
223 self.check_topology(0, 0, 0, 0, 'medium', False)
224
225 self.guest_set_dispatching('0')
226 res = self.vm.qmp('query-s390x-cpu-polarization')
227 self.assertEqual(res['return']['polarization'], 'horizontal')
228 self.check_topology(0, 0, 0, 0, 'medium', False)
229
230
231 def check_polarization(self, polarization):
232 #We need to wait for the change to have been propagated to the kernel
233 exec_command_and_wait_for_pattern(self,
234 "\n".join([
235 "timeout 1 sh -c 'while true",
236 'do',
237 ' syspath="/sys/devices/system/cpu/cpu0/polarization"',
238 ' polarization="$(cat "$syspath")" || exit',
239 f' if [ "$polarization" = "{polarization}" ]; then',
240 ' exit 0',
241 ' fi',
242 ' sleep 0.01',
243 #searched for strings mustn't show up in command, '' to obfuscate
244 "done' && echo succ''ess || echo fail''ure",
245 ]),
246 "success", "failure")
247
248
249 def test_entitlement(self):
250 """
251 This test verifies that QEMU modifies the entitlement
252 after a guest request and that the guest sees the change.
253 """
254 self.set_machine('s390-ccw-virtio')
255 self.kernel_init()
256 self.vm.launch()
257 self.wait_until_booted()
258
259 self.system_init()
260
261 self.check_polarization('horizontal')
262 self.check_topology(0, 0, 0, 0, 'medium', False)
263
264 self.guest_set_dispatching('1')
265 self.check_polarization('vertical:medium')
266 self.check_topology(0, 0, 0, 0, 'medium', False)
267
268 res = self.vm.qmp('set-cpu-topology',
269 {'core-id': 0, 'entitlement': 'low'})
270 self.assertEqual(res['return'], {})
271 self.check_polarization('vertical:low')
272 self.check_topology(0, 0, 0, 0, 'low', False)
273
274 res = self.vm.qmp('set-cpu-topology',
275 {'core-id': 0, 'entitlement': 'medium'})
276 self.assertEqual(res['return'], {})
277 self.check_polarization('vertical:medium')
278 self.check_topology(0, 0, 0, 0, 'medium', False)
279
280 res = self.vm.qmp('set-cpu-topology',
281 {'core-id': 0, 'entitlement': 'high'})
282 self.assertEqual(res['return'], {})
283 self.check_polarization('vertical:high')
284 self.check_topology(0, 0, 0, 0, 'high', False)
285
286 self.guest_set_dispatching('0')
287 self.check_polarization("horizontal")
288 self.check_topology(0, 0, 0, 0, 'high', False)
289
290
291 def test_dedicated(self):
292 """
293 This test verifies that QEMU adjusts the entitlement correctly when a
294 CPU is made dedicated.
295 QEMU retains the entitlement value when horizontal polarization is in effect.
296 For the guest, the field shows the effective value of the entitlement.
297 """
298 self.set_machine('s390-ccw-virtio')
299 self.kernel_init()
300 self.vm.launch()
301 self.wait_until_booted()
302
303 self.system_init()
304
305 self.check_polarization("horizontal")
306
307 res = self.vm.qmp('set-cpu-topology',
308 {'core-id': 0, 'dedicated': True})
309 self.assertEqual(res['return'], {})
310 self.check_topology(0, 0, 0, 0, 'high', True)
311 self.check_polarization("horizontal")
312
313 self.guest_set_dispatching('1')
314 self.check_topology(0, 0, 0, 0, 'high', True)
315 self.check_polarization("vertical:high")
316
317 self.guest_set_dispatching('0')
318 self.check_topology(0, 0, 0, 0, 'high', True)
319 self.check_polarization("horizontal")
320
321
322 def test_socket_full(self):
323 """
324 This test verifies that QEMU does not accept to overload a socket.
325 The socket-id 0 on book-id 0 already contains CPUs 0 and 1 and can
326 not accept any new CPU while socket-id 0 on book-id 1 is free.
327 """
328 self.set_machine('s390-ccw-virtio')
329 self.kernel_init()
330 self.vm.add_args('-smp',
331 '3,drawers=2,books=2,sockets=3,cores=2,maxcpus=24')
332 self.vm.launch()
333 self.wait_until_booted()
334
335 self.system_init()
336
337 res = self.vm.qmp('set-cpu-topology',
338 {'core-id': 2, 'socket-id': 0, 'book-id': 0})
339 self.assertEqual(res['error']['class'], 'GenericError')
340
341 res = self.vm.qmp('set-cpu-topology',
342 {'core-id': 2, 'socket-id': 0, 'book-id': 1})
343 self.assertEqual(res['return'], {})
344
345 def test_dedicated_error(self):
346 """
347 This test verifies that QEMU refuses to lower the entitlement
348 of a dedicated CPU
349 """
350 self.set_machine('s390-ccw-virtio')
351 self.kernel_init()
352 self.vm.launch()
353 self.wait_until_booted()
354
355 self.system_init()
356
357 res = self.vm.qmp('set-cpu-topology',
358 {'core-id': 0, 'dedicated': True})
359 self.assertEqual(res['return'], {})
360
361 self.check_topology(0, 0, 0, 0, 'high', True)
362
363 self.guest_set_dispatching('1')
364
365 self.check_topology(0, 0, 0, 0, 'high', True)
366
367 res = self.vm.qmp('set-cpu-topology',
368 {'core-id': 0, 'entitlement': 'low', 'dedicated': True})
369 self.assertEqual(res['error']['class'], 'GenericError')
370
371 res = self.vm.qmp('set-cpu-topology',
372 {'core-id': 0, 'entitlement': 'low'})
373 self.assertEqual(res['error']['class'], 'GenericError')
374
375 res = self.vm.qmp('set-cpu-topology',
376 {'core-id': 0, 'entitlement': 'medium', 'dedicated': True})
377 self.assertEqual(res['error']['class'], 'GenericError')
378
379 res = self.vm.qmp('set-cpu-topology',
380 {'core-id': 0, 'entitlement': 'medium'})
381 self.assertEqual(res['error']['class'], 'GenericError')
382
383 res = self.vm.qmp('set-cpu-topology',
384 {'core-id': 0, 'entitlement': 'low', 'dedicated': False})
385 self.assertEqual(res['return'], {})
386
387 res = self.vm.qmp('set-cpu-topology',
388 {'core-id': 0, 'entitlement': 'medium', 'dedicated': False})
389 self.assertEqual(res['return'], {})
390
391 def test_move_error(self):
392 """
393 This test verifies that QEMU refuses to move a CPU to an
394 nonexistent location
395 """
396 self.set_machine('s390-ccw-virtio')
397 self.kernel_init()
398 self.vm.launch()
399 self.wait_until_booted()
400
401 self.system_init()
402
403 res = self.vm.qmp('set-cpu-topology', {'core-id': 0, 'drawer-id': 1})
404 self.assertEqual(res['error']['class'], 'GenericError')
405
406 res = self.vm.qmp('set-cpu-topology', {'core-id': 0, 'book-id': 1})
407 self.assertEqual(res['error']['class'], 'GenericError')
408
409 res = self.vm.qmp('set-cpu-topology', {'core-id': 0, 'socket-id': 1})
410 self.assertEqual(res['error']['class'], 'GenericError')
411
412 self.check_topology(0, 0, 0, 0, 'medium', False)
413
414 if __name__ == '__main__':
415 QemuSystemTest.main()