1#!/usr/bin/env python3 2# 3# virtio-balloon tests 4# 5# This work is licensed under the terms of the GNU GPL, version 2 or 6# later. See the COPYING file in the top-level directory. 7 8import time 9 10from qemu_test import QemuSystemTest, Asset 11from qemu_test import wait_for_console_pattern 12from qemu_test import exec_command_and_wait_for_pattern 13 14UNSET_STATS_VALUE = 18446744073709551615 15 16 17class VirtioBalloonx86(QemuSystemTest): 18 19 ASSET_KERNEL = Asset( 20 ('https://archives.fedoraproject.org/pub/archive/fedora/linux/releases' 21 '/31/Server/x86_64/os/images/pxeboot/vmlinuz'), 22 'd4738d03dbbe083ca610d0821d0a8f1488bebbdccef54ce33e3adb35fda00129') 23 24 ASSET_INITRD = Asset( 25 ('https://archives.fedoraproject.org/pub/archive/fedora/linux/releases' 26 '/31/Server/x86_64/os/images/pxeboot/initrd.img'), 27 '277cd6c7adf77c7e63d73bbb2cded8ef9e2d3a2f100000e92ff1f8396513cd8b') 28 29 ASSET_DISKIMAGE = Asset( 30 ('https://archives.fedoraproject.org/pub/archive/fedora/linux/releases' 31 '/31/Cloud/x86_64/images/Fedora-Cloud-Base-31-1.9.x86_64.qcow2'), 32 'e3c1b309d9203604922d6e255c2c5d098a309c2d46215d8fc026954f3c5c27a0') 33 34 DEFAULT_KERNEL_PARAMS = ('root=/dev/vda1 console=ttyS0 net.ifnames=0 ' 35 'rd.rescue quiet') 36 37 def wait_for_console_pattern(self, success_message, vm=None): 38 wait_for_console_pattern( 39 self, 40 success_message, 41 failure_message="Kernel panic - not syncing", 42 vm=vm, 43 ) 44 45 def mount_root(self): 46 self.wait_for_console_pattern('Entering emergency mode.') 47 prompt = '# ' 48 self.wait_for_console_pattern(prompt) 49 50 # Synchronize on virtio-block driver creating the root device 51 exec_command_and_wait_for_pattern(self, 52 "while ! (dmesg -c | grep vda:) ; do sleep 1 ; done", 53 "vda1") 54 55 exec_command_and_wait_for_pattern(self, 'mount /dev/vda1 /sysroot', 56 prompt) 57 exec_command_and_wait_for_pattern(self, 'chroot /sysroot', 58 prompt) 59 exec_command_and_wait_for_pattern(self, "modprobe virtio-balloon", 60 prompt) 61 62 def assert_initial_stats(self): 63 ret = self.vm.qmp('qom-get', 64 {'path': '/machine/peripheral/balloon', 65 'property': 'guest-stats'})['return'] 66 when = ret.get('last-update') 67 assert when == 0 68 stats = ret.get('stats') 69 for name, val in stats.items(): 70 assert val == UNSET_STATS_VALUE 71 72 def assert_running_stats(self, then): 73 # We told the QEMU to refresh stats every 100ms, but 74 # there can be a delay between virtio-ballon driver 75 # being modprobed and seeing the first stats refresh 76 # Retry a few times for robustness under heavy load 77 retries = 10 78 when = 0 79 while when == 0 and retries: 80 ret = self.vm.qmp('qom-get', 81 {'path': '/machine/peripheral/balloon', 82 'property': 'guest-stats'})['return'] 83 when = ret.get('last-update') 84 if when == 0: 85 retries = retries - 1 86 time.sleep(0.5) 87 88 now = time.time() 89 90 assert when > then and when < now 91 stats = ret.get('stats') 92 # Stat we expect this particular Kernel to have set 93 expectData = [ 94 "stat-available-memory", 95 "stat-disk-caches", 96 "stat-free-memory", 97 "stat-htlb-pgalloc", 98 "stat-htlb-pgfail", 99 "stat-major-faults", 100 "stat-minor-faults", 101 "stat-swap-in", 102 "stat-swap-out", 103 "stat-total-memory", 104 ] 105 for name, val in stats.items(): 106 if name in expectData: 107 assert val != UNSET_STATS_VALUE 108 else: 109 assert val == UNSET_STATS_VALUE 110 111 def test_virtio_balloon_stats(self): 112 self.set_machine('q35') 113 self.require_accelerator("kvm") 114 kernel_path = self.ASSET_KERNEL.fetch() 115 initrd_path = self.ASSET_INITRD.fetch() 116 diskimage_path = self.ASSET_DISKIMAGE.fetch() 117 118 self.vm.set_console() 119 self.vm.add_args("-S") 120 self.vm.add_args("-cpu", "max") 121 self.vm.add_args("-m", "2G") 122 # Slow down BIOS phase with boot menu, so that after a system 123 # reset, we can reliably catch the clean stats again in BIOS 124 # phase before the guest OS launches 125 self.vm.add_args("-boot", "menu=on") 126 self.vm.add_args("-accel", "kvm") 127 self.vm.add_args("-device", "virtio-balloon,id=balloon") 128 self.vm.add_args('-drive', 129 f'file={diskimage_path},if=none,id=drv0,snapshot=on') 130 self.vm.add_args('-device', 'virtio-blk-pci,bus=pcie.0,' + 131 'drive=drv0,id=virtio-disk0,bootindex=1') 132 133 self.vm.add_args( 134 "-kernel", 135 kernel_path, 136 "-initrd", 137 initrd_path, 138 "-append", 139 self.DEFAULT_KERNEL_PARAMS 140 ) 141 self.vm.launch() 142 143 # Poll stats at 100ms 144 self.vm.qmp('qom-set', 145 {'path': '/machine/peripheral/balloon', 146 'property': 'guest-stats-polling-interval', 147 'value': 100 }) 148 149 # We've not run any guest code yet, neither BIOS or guest, 150 # so stats should be all default values 151 self.assert_initial_stats() 152 153 self.vm.qmp('cont') 154 155 then = time.time() 156 self.mount_root() 157 self.assert_running_stats(then) 158 159 # Race window between these two commands, where we 160 # rely on '-boot menu=on' to (hopefully) ensure we're 161 # still executing the BIOS when QEMU processes the 162 # 'stop', and thus have not loaded the virtio-balloon 163 # driver in the guest 164 self.vm.qmp('system_reset') 165 self.vm.qmp('stop') 166 167 # If the above assumption held, we're in BIOS now and 168 # stats should be all back at their default values 169 self.assert_initial_stats() 170 self.vm.qmp('cont') 171 172 then = time.time() 173 self.mount_root() 174 self.assert_running_stats(then) 175 176 177if __name__ == '__main__': 178 QemuSystemTest.main() 179