Add a functional test that hot-plugs and hot-unplugs a virtio-blk-pci
disk on the pseries machine.  It runs with TCG, and also with KVM if
KVM is enabled.

On pseries a PCI device added through QMP is only enumerated by the
guest once rtas_errd (ppc64-diag) handles the RTAS hotplug event and
runs drmgr (powerpc-utils).  Neither is in the buildroot image used by
the other pseries tests, so the test uses rootfs-hotplug.ext2: the same
image with those two tools added.

The test boots with a second PHB (phb1) present so that the guest
registers its slots, adds a null-co backend, hot-plugs virtio-blk-pci
into phb1.0 and waits for /dev/vda, then unplugs it and waits for both
the QMP DEVICE_DELETED event and /dev/vda to go away.  The root drive is
used with snapshot=on so the cached asset is not modified by the guest.

Signed-off-by: Anushree Mathur <[email protected]>
---
Hi all,

This adds a functional test for virtio-blk hotplug on the pseries
machine, with a TCG and a KVM variant.

The test boots pseries with a second PCI host bridge (phb1) present at
boot, hot-plugs a virtio-blk-pci device into it over QMP, and waits for
/dev/vda to show up in the guest.  It then unplugs the device and waits
for the DEVICE_DELETED event and for /dev/vda to disappear again.

On pseries the guest does not enumerate a hot-plugged PCI device by
itself: rtas_errd (ppc64-diag) has to handle the RTAS hotplug event and
run drmgr (powerpc-utils).  The buildroot image the other pseries tests
use has neither, so the test needs a rootfs that includes them.

I am sending this as an RFC because that rootfs is not available yet.
It is a new file, rootfs-hotplug.ext2, which I proposed for the
qemu-ppc-boot repository next to the existing pseries images:

  https://github.com/legoater/qemu-ppc-boot/pull/5

Until that is merged, the Asset URL in the test returns 404.  The sha256
in the test matches the image in that pull request.  The image is the
existing 2023.11 ppc64le pseries rootfs with rtas_errd, drmgr and their
libraries added, built with the legoater/buildroot qemu-ppc toolchain.

Tested on a ppc64le POWER host with both TCG and KVM; checkpatch.pl and
pylint are clean.

 MAINTAINERS                                |   1 +
 tests/functional/ppc64/meson.build         |   2 +
 tests/functional/ppc64/test_hotplug_blk.py | 188 +++++++++++++++++++++
 3 files changed, 191 insertions(+)
 create mode 100644 tests/functional/ppc64/test_hotplug_blk.py

diff --git a/MAINTAINERS b/MAINTAINERS
index 3b12ba90f2..806d8e6ce6 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1704,6 +1704,7 @@ F: tests/qtest/libqos/rtas*
 F: tests/functional/ppc64/test_pseries.py
 F: tests/functional/ppc64/test_hv.py
 F: tests/functional/ppc64/test_tuxrun.py
+F: tests/functional/ppc64/test_hotplug_blk.py
 
 PowerNV (Non-Virtualized)
 M: Nicholas Piggin <[email protected]>
diff --git a/tests/functional/ppc64/meson.build 
b/tests/functional/ppc64/meson.build
index cb3c745624..e1e8211dcb 100644
--- a/tests/functional/ppc64/meson.build
+++ b/tests/functional/ppc64/meson.build
@@ -2,6 +2,7 @@
 
 test_ppc64_timeouts = {
   'fadump' : 480,
+  'hotplug_blk' : 600,
   'hv' : 1000,
   'mac99' : 120,
   'openbsd' : 240,
@@ -19,6 +20,7 @@ tests_ppc64_system_quick = [
 tests_ppc64_system_thorough = [
   'e500',
   'fadump',
+  'hotplug_blk',
   'hv',
   'mac99',
   'openbsd',
diff --git a/tests/functional/ppc64/test_hotplug_blk.py 
b/tests/functional/ppc64/test_hotplug_blk.py
new file mode 100644
index 0000000000..2737e4b486
--- /dev/null
+++ b/tests/functional/ppc64/test_hotplug_blk.py
@@ -0,0 +1,188 @@
+#!/usr/bin/env python3
+#
+# Functional test that hot-plugs and hot-unplugs a virtio-blk-pci disk
+# on the pseries machine type.
+#
+# Copyright (c) 2025 IBM Corporation
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+import platform
+
+from qemu_test import LinuxKernelTest, Asset
+from qemu_test import exec_command_and_wait_for_pattern
+
+
+class PseriesHotplugBlk(LinuxKernelTest):
+    """
+    Test virtio-blk-pci hotplug/unplug on the pseries machine type.
+
+    A pre-built rootfs image (based on the buildroot image used by
+    test_hotplug_cpu.py) is used as the guest OS.  The image includes
+    rtas_errd from ppc64-diag, which is required on pseries to call the
+    ibm,configure-connector RTAS function that makes a QMP-hotplugged
+    PCI device visible to the kernel.
+
+    ppc64-diag is not packaged in Alpine Linux, so the Alpine+apk
+    approach used by test_hv.py cannot be used here.  Instead a
+    self-contained rootfs with rtas_errd and drmgr (from powerpc-utils)
+    already installed is referenced as an Asset, following the same
+    pattern as test_hotplug_cpu.py.
+
+    The test is run with both TCG and KVM; the KVM variant needs a
+    ppc64 host with kvm-hv loaded and is skipped automatically otherwise.
+
+    A second spapr-pci-host-bridge (phb1, index=1) is present at VM
+    launch so the guest kernel registers its PCI slots with rpaphp
+    during boot-time PCI probe.  Runtime DLPAR hotplug of
+    virtio-blk-pci into phb1.0 then works via the rtas_errd path.
+    """
+
+    timeout = 600
+
+    ASSET_KERNEL = Asset(
+        ('https://github.com/legoater/qemu-ppc-boot/raw/refs/heads/main/'
+         'buildroot/qemu_ppc64le_pseries-2023.11-8-gdcd9f0f6eb-20240104/'
+         'vmlinux'),
+        'fc20d5820ad83ed514ac5f88af7e2692d47ab7f36dab290cdb137d05b98502f5')
+
+    # Pre-built rootfs based on the buildroot pseries image above, with
+    # rtas_errd (from ppc64-diag) and drmgr (from powerpc-utils) added.
+    # rtas_errd is required to process RTAS hotplug events on pseries and
+    # it execs drmgr to configure the device; without them rpaphp
+    # registers the slots but the device is never enumerated by the kernel.
+    ASSET_ROOTFS = Asset(
+        ('https://github.com/legoater/qemu-ppc-boot/raw/refs/heads/main/'
+         'buildroot/qemu_ppc64le_pseries-2023.11-8-gdcd9f0f6eb-20240104/'
+         'rootfs-hotplug.ext2'),
+        '01ab3421d313e7765176eb27193fdb5837281dc3c8484eed140de8a3e65908eb')
+
+    def _boot_pseries(self, accel):
+        """Boot pseries with phb1 present and rtas_errd running.
+
+        accel must be 'tcg' or 'kvm'.
+        """
+        kernel_path = self.ASSET_KERNEL.fetch()
+        rootfs_path = self.ASSET_ROOTFS.fetch()
+
+        self.set_machine('pseries')
+        if accel == 'tcg':
+            self.vm.add_args('-accel', 'tcg,thread=multi')
+        else:
+            self.vm.add_args('-accel', 'kvm')
+        self.vm.add_args('-smp', '2', '-m', '512M')
+        # Second PHB present at boot so the guest kernel registers its
+        # slots with rpaphp.  Required for runtime DLPAR hotplug of
+        # virtio-blk-pci into phb1.0.
+        self.vm.add_args('-device', 'spapr-pci-host-bridge,id=phb1,index=1')
+        # snapshot=on keeps the cached rootfs asset unmodified
+        self.vm.add_args(
+            '-drive',
+            f'file={rootfs_path},if=scsi,index=0,format=raw,snapshot=on')
+        self.vm.add_args(
+            '-kernel', kernel_path,
+            '-append', 'root=/dev/sda console=hvc0 rootwait printk.time=0',
+        )
+        self.vm.set_console()
+        self.vm.launch()
+
+        self.wait_for_console_pattern('buildroot login:')
+        exec_command_and_wait_for_pattern(self, 'root', '#')
+
+        # Start rtas_errd so that RTAS hotplug events are processed.
+        # rtas_errd daemonizes by default (no -d flag).  Run mkdir and
+        # rtas_errd unconditionally then echo a sentinel so the pattern
+        # match never depends on their exit codes.
+        exec_command_and_wait_for_pattern(
+            self,
+            'touch /var/log/platform; mkdir -p /var/log/dump;'
+            ' /usr/sbin/rtas_errd;'
+            ' echo rtas_errd_started',
+            'rtas_errd_started',
+        )
+
+    def _do_blk_hotplug(self):
+
+        # Confirm /dev/vda does not exist before hotplug.
+        exec_command_and_wait_for_pattern(
+            self,
+            'test -b /dev/vda && echo present || echo absent',
+            'absent',
+        )
+
+        # Add a null-co block backend (no image file needed).
+        self.vm.cmd(
+            'blockdev-add',
+            **{
+                'driver': 'null-co',
+                'size': 1073741824,
+                'node-name': 'testdisk',
+            },
+        )
+
+        # Hotplug virtio-blk-pci into phb1.0.  The RTAS hotplug event
+        # (type 229) is delivered to the guest; rtas_errd calls
+        # ibm,configure-connector, the kernel enumerates the device and
+        # udev creates /dev/vda.
+        self.vm.cmd(
+            'device_add',
+            driver='virtio-blk-pci',
+            drive='testdisk',
+            id='hotblk0',
+            bus='phb1.0',
+            addr='0x0',
+        )
+
+        # Poll until /dev/vda appears.  Use a shell variable sentinel so
+        # the string 'vda_present' never appears in the typed command,
+        # avoiding a false match on the echoed command text.
+        exec_command_and_wait_for_pattern(
+            self,
+            'until test -b /dev/vda; do sleep 0.2; done'
+            '; R=present; echo vda_$R',
+            'vda_present',
+        )
+
+        # Unplug the device and wait for the QMP deletion event.
+        self.vm.cmd('device_del', id='hotblk0')
+        self.vm.event_wait(
+            'DEVICE_DELETED',
+            match={'data': {'device': 'hotblk0'}},
+        )
+
+        # Poll until /dev/vda disappears.
+        exec_command_and_wait_for_pattern(
+            self,
+            'while test -b /dev/vda; do sleep 0.2; done'
+            '; R=absent; echo vda_$R',
+            'vda_absent',
+        )
+
+    def test_pseries_hotplug_blk_tcg(self):
+        """Hotplug then unplug a virtio-blk-pci device on pseries (TCG)."""
+        self.require_accelerator('tcg')
+        self._boot_pseries('tcg')
+        self._do_blk_hotplug()
+
+    def test_pseries_hotplug_blk_kvm(self):
+        """Hotplug then unplug a virtio-blk-pci device on pseries (KVM).
+
+        Requires a ppc64 host with kvm-hv loaded; skipped automatically
+        on other hosts.
+        """
+        self.require_accelerator('kvm')
+        # On ppc64 the base kvm module can be loaded (and /dev/kvm created)
+        # without kvm-hv, so checking /dev/kvm alone is not sufficient.
+        if platform.machine() in ('ppc64le', 'ppc64'):
+            try:
+                with open('/proc/modules', encoding='utf-8') as f:
+                    if not any(line.split()[0] == 'kvm_hv' for line in f):
+                        self.skipTest('kvm-hv module is not loaded')
+            except OSError:
+                pass
+        self._boot_pseries('kvm')
+        self._do_blk_hotplug()
+
+
+if __name__ == '__main__':
+    PseriesHotplugBlk.main()
-- 
2.55.0


Reply via email to