diff --git a/MAINTAINERS b/MAINTAINERS index f7219d5d99..6b1ed232eb 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2338,6 +2338,7 @@ F: tests/qtest/virtio-scsi-test.c F: tests/qtest/fuzz-virtio-scsi-test.c F: tests/qtest/am53c974-test.c F: tests/qtest/fuzz-lsi53c895a-test.c +F: tests/functional/x86_64/test_hotplug_scsi.py T: git https://github.com/bonzini/qemu.git scsi-next SSI diff --git a/tests/functional/arm/test_aspeed_anacapa.py b/tests/functional/arm/test_aspeed_anacapa.py index b16c6035c9..363b4c2a1d 100644 --- a/tests/functional/arm/test_aspeed_anacapa.py +++ b/tests/functional/arm/test_aspeed_anacapa.py @@ -5,10 +5,10 @@ # SPDX-License-Identifier: GPL-2.0-or-later from qemu_test import Asset -from aspeed import FacebookAspeedTest +from aspeed import AspeedTest -class AnacapaMachine(FacebookAspeedTest): +class AnacapaMachine(AspeedTest): ASSET_ANACAPA_FLASH = Asset( 'https://github.com/legoater/qemu-aspeed-boot/raw/refs/heads/master/images/anacapa-bmc/openbmc-20260616025349/obmc-phosphor-image-anacapa-20260616025349.static.mtd.xz', @@ -22,4 +22,4 @@ def test_arm_ast2600_anacapa_openbmc(self): soc='AST2600 rev A3') if __name__ == '__main__': - FacebookAspeedTest.main() + AspeedTest.main() diff --git a/tests/functional/arm/test_aspeed_bletchley.py b/tests/functional/arm/test_aspeed_bletchley.py index 3000d0c302..5a60b24b3d 100755 --- a/tests/functional/arm/test_aspeed_bletchley.py +++ b/tests/functional/arm/test_aspeed_bletchley.py @@ -5,10 +5,10 @@ # SPDX-License-Identifier: GPL-2.0-or-later from qemu_test import Asset -from aspeed import FacebookAspeedTest +from aspeed import AspeedTest -class BletchleyMachine(FacebookAspeedTest): +class BletchleyMachine(AspeedTest): ASSET_BLETCHLEY_FLASH = Asset( 'https://github.com/legoater/qemu-aspeed-boot/raw/master/images/bletchley-bmc/openbmc-20250128071329/obmc-phosphor-image-bletchley-20250128071329.static.mtd.xz', @@ -22,4 +22,4 @@ def test_arm_ast2600_bletchley_openbmc(self): soc='AST2600 rev A3') if __name__ == '__main__': - FacebookAspeedTest.main() + AspeedTest.main() diff --git a/tests/functional/arm/test_aspeed_catalina.py b/tests/functional/arm/test_aspeed_catalina.py index 2694e4b005..dc2f24e7b4 100755 --- a/tests/functional/arm/test_aspeed_catalina.py +++ b/tests/functional/arm/test_aspeed_catalina.py @@ -5,10 +5,10 @@ # SPDX-License-Identifier: GPL-2.0-or-later from qemu_test import Asset -from aspeed import FacebookAspeedTest +from aspeed import AspeedTest -class CatalinaMachine(FacebookAspeedTest): +class CatalinaMachine(AspeedTest): ASSET_CATALINA_FLASH = Asset( 'https://github.com/legoater/qemu-aspeed-boot/raw/a866feb5ef81245b4827a214584bf6bcc72939f6/images/catalina-bmc/obmc-phosphor-image-catalina-20250619123021.static.mtd.xz', @@ -22,4 +22,4 @@ def test_arm_ast2600_catalina_openbmc(self): soc='AST2600 rev A3') if __name__ == '__main__': - FacebookAspeedTest.main() + AspeedTest.main() diff --git a/tests/functional/arm/test_aspeed_gb200nvl_bmc.py b/tests/functional/arm/test_aspeed_gb200nvl_bmc.py index e5f2dce0f5..f9e736d047 100755 --- a/tests/functional/arm/test_aspeed_gb200nvl_bmc.py +++ b/tests/functional/arm/test_aspeed_gb200nvl_bmc.py @@ -19,8 +19,7 @@ def test_arm_aspeed_gb200_openbmc(self): self.do_test_arm_aspeed_openbmc('gb200nvl-bmc', image=image_path, uboot='2019.04', cpu_id='0xf00', - soc='AST2600 rev A3', - image_hostname='gb200nvl-obmc') + soc='AST2600 rev A3') if __name__ == '__main__': AspeedTest.main() diff --git a/tests/functional/aspeed.py b/tests/functional/aspeed.py index 88b6590934..076da1036c 100644 --- a/tests/functional/aspeed.py +++ b/tests/functional/aspeed.py @@ -8,14 +8,7 @@ class AspeedTest(LinuxKernelTest): def do_test_arm_aspeed_openbmc(self, machine, image, uboot='2019.04', - cpu_id='0x0', soc='AST2500 rev A1', - image_hostname=None): - # Allow for the image hostname to not end in "-bmc" - if image_hostname is not None: - hostname = image_hostname - else: - hostname = machine.removesuffix('-bmc') - + cpu_id='0x0', soc='AST2500 rev A1'): self.set_machine(machine) self.vm.set_console() self.vm.add_args('-drive', f'file={image},if=mtd,format=raw', @@ -28,10 +21,10 @@ def do_test_arm_aspeed_openbmc(self, machine, image, uboot='2019.04', self.wait_for_console_pattern(f'Booting Linux on physical CPU {cpu_id}') self.wait_for_console_pattern(f'ASPEED {soc}') self.wait_for_console_pattern('/init as init process') - self.wait_for_boot_complete(hostname) + self.wait_for_boot_complete() - def wait_for_boot_complete(self, hostname): - self.wait_for_console_pattern(f'systemd[1]: Hostname set to <{hostname}>.') + def wait_for_boot_complete(self): + self.wait_for_console_pattern('login:') def do_test_arm_aspeed_buildroot_start(self, image, cpu_id, pattern='Aspeed EVB'): self.require_netdev('user') @@ -71,9 +64,3 @@ def generate_otpmem_image(self): with open(path, "wb") as f: f.write(pattern) return path - - -class FacebookAspeedTest(AspeedTest): - - def wait_for_boot_complete(self, hostname): - self.wait_for_console_pattern(f'{hostname} login:') diff --git a/tests/functional/qemu_test/testcase.py b/tests/functional/qemu_test/testcase.py index eaec1bea13..4912a47a46 100644 --- a/tests/functional/qemu_test/testcase.py +++ b/tests/functional/qemu_test/testcase.py @@ -314,13 +314,32 @@ def setUp(self): console_log.addHandler(self._console_log_fh) def set_machine(self, machinename): - # TODO: We should use QMP to get the list of available machines - if not self._machinehelp: - self._machinehelp = run( - [self.qemu_bin, '-M', 'help'], - capture_output=True, check=True, encoding='utf8').stdout - if self._machinehelp.find(machinename) < 0: + cls = type(self) + + if not hasattr(cls, "_machines"): + tmp_vm = QEMUMachine(self.qemu_bin) + tmp_vm.set_machine('none') + + try: + tmp_vm.launch() + resp = tmp_vm.qmp('query-machines') + + machines = resp.get('return', []) + cls._machines = [ + m.get('name') for m in machines if 'name' in m + ] + + finally: + try: + tmp_vm.shutdown() + except Exception: + pass + + self._machines = cls._machines + + if machinename not in self._machines: self.skipTest('no support for machine ' + machinename) + self.machine = machinename def require_accelerator(self, accelerator): diff --git a/tests/functional/x86_64/meson.build b/tests/functional/x86_64/meson.build index fd77f19d72..27b31f2e96 100644 --- a/tests/functional/x86_64/meson.build +++ b/tests/functional/x86_64/meson.build @@ -31,6 +31,7 @@ endif tests_x86_64_system_thorough = [ 'acpi_bits', 'hotplug_blk', + 'hotplug_scsi', 'hotplug_cpu', 'intel_iommu', 'kvm_xen', diff --git a/tests/functional/x86_64/test_hotplug_scsi.py b/tests/functional/x86_64/test_hotplug_scsi.py new file mode 100755 index 0000000000..10fd306616 --- /dev/null +++ b/tests/functional/x86_64/test_hotplug_scsi.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +# +# Functional test that hotplugs a virtio scsi disk and checks it on a Linux +# guest +# +# SPDX-License-Identifier: GPL-2.0-or-later + +from qemu_test import LinuxKernelTest, Asset, exec_command_and_wait_for_pattern + +class HotPlugScsi(LinuxKernelTest): + ASSET_KERNEL = Asset( + ('https://archives.fedoraproject.org/pub/archive/fedora/linux/releases' + '/31/Server/x86_64/os/images/pxeboot/vmlinuz'), + 'd4738d03dbbe083ca610d0821d0a8f1488bebbdccef54ce33e3adb35fda00129') + + ASSET_INITRD = Asset( + ('https://archives.fedoraproject.org/pub/archive/fedora/linux/releases' + '/31/Server/x86_64/os/images/pxeboot/initrd.img'), + '277cd6c7adf77c7e63d73bbb2cded8ef9e2d3a2f100000e92ff1f8396513cd8b') + + def blockdev_add(self) -> None: + self.vm.cmd('blockdev-add', **{ + 'driver': 'null-co', + 'size': 1073741824, + 'node-name': 'disk0' + }) + + def add_scsi_controller(self) -> None: + self.vm.cmd('device_add', { + 'driver': 'virtio-scsi-pci', + 'id': 'scsi0', + 'bus': 'pci.1', + 'addr': '1', + }) + + def assert_sda(self) -> None: + exec_command_and_wait_for_pattern(self, 'while ! test -e /sys/block/sda ;' + ' do sleep 0.2 ; done', '# ') + + def assert_no_sda(self) -> None: + exec_command_and_wait_for_pattern(self, 'while test -e /sys/block/sda ;' + ' do sleep 0.2 ; done', '# ') + + def plug(self) -> None: + args = { + 'driver':'scsi-hd', + 'drive':'disk0', + 'bus':'scsi0.0', + 'id':'scsi-disk0', + } + + self.assert_no_sda() + self.vm.cmd('device_add', args) + self.wait_for_console_pattern('[sda] Attached SCSI disk') + self.assert_sda() + + def unplug(self) -> None: + self.vm.cmd('device_del', id='scsi-disk0') + + self.vm.event_wait('DEVICE_DELETED', 1.0, + match={'data': {'device': 'scsi-disk0'}}) + + self.assert_no_sda() + + def test(self) -> None: + self.require_accelerator('kvm') + self.set_machine('q35') + self.vm.add_args('-accel', 'kvm') + self.vm.add_args('-device', 'pcie-pci-bridge,id=pci.1,bus=pcie.0') + self.vm.add_args('-m', '1G') + self.vm.add_args('-append', 'console=ttyS0 rd.rescue') + + self.launch_kernel(self.ASSET_KERNEL.fetch(), + self.ASSET_INITRD.fetch(), + wait_for='Entering emergency mode.') + self.wait_for_console_pattern('# ') + + self.blockdev_add() + self.add_scsi_controller() + self.plug() + self.unplug() + + +if __name__ == '__main__': + LinuxKernelTest.main() +