| 1 | #!/usr/bin/env python3 |
| 2 | # |
| 3 | # Tests that specifically try to exercise hypervisor features of the |
| 4 | # target machines. powernv supports the Power hypervisor ISA, and |
| 5 | # pseries supports the nested-HV hypervisor spec. |
| 6 | # |
| 7 | # Copyright (c) 2023 IBM Corporation |
| 8 | # |
| 9 | # This work is licensed under the terms of the GNU GPL, version 2 or |
| 10 | # later. See the COPYING file in the top-level directory. |
| 11 | |
| 12 | import os |
| 13 | import subprocess |
| 14 | |
| 15 | from datetime import datetime |
| 16 | from qemu_test import QemuSystemTest, Asset |
| 17 | from qemu_test import wait_for_console_pattern, exec_command |
| 18 | from qemu_test import skipIfMissingCommands, skipBigDataTest |
| 19 | from qemu_test import exec_command_and_wait_for_pattern |
| 20 | |
| 21 | # Alpine is a light weight distro that supports QEMU. These tests boot |
| 22 | # that on the machine then run a QEMU guest inside it in KVM mode, |
| 23 | # that runs the same Alpine distro image. |
| 24 | # QEMU packages are downloaded and installed on each test. That's not a |
| 25 | # large download, but it may be more polite to create qcow2 image with |
| 26 | # QEMU already installed and use that. |
| 27 | # XXX: The order of these tests seems to matter, see git blame. |
| 28 | @skipIfMissingCommands("xorriso") |
| 29 | @skipBigDataTest() |
| 30 | class HypervisorTest(QemuSystemTest): |
| 31 | |
| 32 | panic_message = 'Kernel panic - not syncing' |
| 33 | good_message = 'VFS: Cannot open root device' |
| 34 | |
| 35 | ASSET_ISO = Asset( |
| 36 | ('https://dl-cdn.alpinelinux.org/alpine/v3.21/' |
| 37 | 'releases/ppc64le/alpine-standard-3.21.0-ppc64le.iso'), |
| 38 | '7651ab4e3027604535c0b36e86c901b4695bf8fe97b908f5b48590f6baae8f30') |
| 39 | |
| 40 | def extract_from_iso(self, iso, path): |
| 41 | """ |
| 42 | Extracts a file from an iso file into the test workdir |
| 43 | |
| 44 | :param iso: path to the iso file |
| 45 | :param path: path within the iso file of the file to be extracted |
| 46 | :returns: path of the extracted file |
| 47 | """ |
| 48 | filename = self.scratch_file(os.path.basename(path)) |
| 49 | |
| 50 | cmd = f"xorriso -osirrox on -indev {iso} -cpx {path} {filename}" |
| 51 | subprocess.run(cmd.split(), check=True, |
| 52 | stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) |
| 53 | |
| 54 | os.chmod(filename, 0o600) |
| 55 | |
| 56 | return filename |
| 57 | |
| 58 | def setUp(self): |
| 59 | super().setUp() |
| 60 | |
| 61 | self.iso_path = self.ASSET_ISO.fetch() |
| 62 | self.vmlinuz = self.extract_from_iso(self.iso_path, '/boot/vmlinuz-lts') |
| 63 | self.initramfs = self.extract_from_iso(self.iso_path, '/boot/initramfs-lts') |
| 64 | |
| 65 | def do_start_alpine(self): |
| 66 | self.vm.set_console() |
| 67 | self.vm.add_args("-kernel", self.vmlinuz) |
| 68 | self.vm.add_args("-initrd", self.initramfs) |
| 69 | self.vm.add_args("-smp", "4", "-m", "2g") |
| 70 | self.vm.add_args("-drive", f"file={self.iso_path},format=raw,if=none," |
| 71 | "id=drive0,read-only=true") |
| 72 | |
| 73 | self.vm.launch() |
| 74 | ps1='localhost:~#' |
| 75 | wait_for_console_pattern(self, 'localhost login:') |
| 76 | exec_command_and_wait_for_pattern(self, 'root', ps1) |
| 77 | # If the time is wrong, SSL certificates can fail. |
| 78 | cmd='date -s "' + datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S' + '"') |
| 79 | exec_command_and_wait_for_pattern(self, cmd, ps1) |
| 80 | ps1='alpine:~#' |
| 81 | exec_command_and_wait_for_pattern(self, 'setup-alpine -qe', ps1) |
| 82 | exec_command_and_wait_for_pattern(self, 'setup-apkrepos -c1', ps1) |
| 83 | exec_command_and_wait_for_pattern(self, 'apk update', ps1) |
| 84 | # Could upgrade here but it usually should not be necessary |
| 85 | # exec_command_and_wait_for_pattern(self, 'apk upgrade --available', ps1) |
| 86 | |
| 87 | def do_stop_alpine(self): |
| 88 | exec_command(self, 'echo "TEST ME"') |
| 89 | wait_for_console_pattern(self, 'alpine:~#') |
| 90 | exec_command(self, 'poweroff') |
| 91 | wait_for_console_pattern(self, 'reboot: Power down') |
| 92 | self.vm.wait() |
| 93 | |
| 94 | def do_setup_kvm(self): |
| 95 | ps1='alpine:~#' |
| 96 | exec_command_and_wait_for_pattern(self, 'apk add qemu-system-ppc64', ps1) |
| 97 | exec_command_and_wait_for_pattern(self, 'modprobe kvm-hv', ps1) |
| 98 | |
| 99 | # This uses the host's block device as the source file for guest block |
| 100 | # device for install media. This is a bit hacky but allows reuse of the |
| 101 | # iso without having a passthrough filesystem configured. |
| 102 | def do_test_kvm(self, hpt=False): |
| 103 | if hpt: |
| 104 | append = 'disable_radix' |
| 105 | else: |
| 106 | append = '' |
| 107 | exec_command(self, 'qemu-system-ppc64 -nographic -smp 2 -m 1g ' |
| 108 | '-machine pseries,x-vof=on,accel=kvm ' |
| 109 | '-machine cap-cfpc=broken,cap-sbbc=broken,' |
| 110 | 'cap-ibs=broken,cap-ccf-assist=off ' |
| 111 | '-drive file=/dev/nvme0n1,format=raw,readonly=on ' |
| 112 | '-initrd /media/nvme0n1/boot/initramfs-lts ' |
| 113 | '-kernel /media/nvme0n1/boot/vmlinuz-lts ' |
| 114 | '-append \'usbcore.nousb ' + append + '\'') |
| 115 | # Alpine 3.21 kernel seems to crash in XHCI USB driver. |
| 116 | ps1='localhost:~#' |
| 117 | wait_for_console_pattern(self, 'localhost login:') |
| 118 | exec_command_and_wait_for_pattern(self, 'root', ps1) |
| 119 | exec_command(self, 'poweroff') |
| 120 | wait_for_console_pattern(self, 'reboot: Power down') |
| 121 | # Now wait for the host's prompt to come back |
| 122 | wait_for_console_pattern(self, 'alpine:~#') |
| 123 | |
| 124 | def test_hv_pseries(self): |
| 125 | self.require_accelerator("tcg") |
| 126 | self.require_netdev('user') |
| 127 | self.set_machine('pseries') |
| 128 | self.vm.add_args("-accel", "tcg,thread=multi") |
| 129 | self.vm.add_args('-device', 'nvme,serial=1234,drive=drive0') |
| 130 | self.vm.add_args("-machine", "x-vof=on,cap-nested-hv=on") |
| 131 | self.do_start_alpine() |
| 132 | self.do_setup_kvm() |
| 133 | self.do_test_kvm() |
| 134 | self.do_stop_alpine() |
| 135 | |
| 136 | def test_hv_pseries_kvm(self): |
| 137 | self.require_accelerator("kvm") |
| 138 | self.require_netdev('user') |
| 139 | self.set_machine('pseries') |
| 140 | self.vm.add_args("-accel", "kvm") |
| 141 | self.vm.add_args('-device', 'nvme,serial=1234,drive=drive0') |
| 142 | self.vm.add_args("-machine", "x-vof=on,cap-nested-hv=on,cap-ccf-assist=off") |
| 143 | self.do_start_alpine() |
| 144 | self.do_setup_kvm() |
| 145 | self.do_test_kvm() |
| 146 | self.do_stop_alpine() |
| 147 | |
| 148 | def test_hv_powernv(self): |
| 149 | self.require_accelerator("tcg") |
| 150 | self.require_netdev('user') |
| 151 | self.set_machine('powernv') |
| 152 | self.vm.add_args("-accel", "tcg,thread=multi") |
| 153 | self.vm.add_args('-device', 'nvme,bus=pcie.2,addr=0x0,serial=1234,drive=drive0', |
| 154 | '-device', 'e1000e,netdev=net0,mac=C0:FF:EE:00:00:02,bus=pcie.0,addr=0x0', |
| 155 | '-netdev', 'user,id=net0,hostfwd=::20022-:22,hostname=alpine') |
| 156 | self.do_start_alpine() |
| 157 | self.do_setup_kvm() |
| 158 | self.do_test_kvm() |
| 159 | self.do_test_kvm(True) |
| 160 | self.do_stop_alpine() |
| 161 | |
| 162 | if __name__ == '__main__': |
| 163 | QemuSystemTest.main() |