| #!/usr/bin/env python3 |
| # |
| # virtio-balloon tests |
| # |
| # This work is licensed under the terms of the GNU GPL, version 2 or |
| # later. See the COPYING file in the top-level directory. |
| |
| import time |
| |
| from qemu_test import QemuSystemTest, Asset |
| from qemu_test import wait_for_console_pattern |
| from qemu_test import exec_command_and_wait_for_pattern |
| |
| UNSET_STATS_VALUE = 18446744073709551615 |
| |
| |
| class VirtioBalloonx86(QemuSystemTest): |
| |
| 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') |
| |
| ASSET_DISKIMAGE = Asset( |
| ('https://archives.fedoraproject.org/pub/archive/fedora/linux/releases' |
| '/31/Cloud/x86_64/images/Fedora-Cloud-Base-31-1.9.x86_64.qcow2'), |
| 'e3c1b309d9203604922d6e255c2c5d098a309c2d46215d8fc026954f3c5c27a0') |
| |
| DEFAULT_KERNEL_PARAMS = ('root=/dev/vda1 console=ttyS0 net.ifnames=0 ' |
| 'rd.rescue quiet') |
| |
| def wait_for_console_pattern(self, success_message, vm=None): |
| wait_for_console_pattern( |
| self, |
| success_message, |
| failure_message="Kernel panic - not syncing", |
| vm=vm, |
| ) |
| |
| def mount_root(self): |
| self.wait_for_console_pattern('Entering emergency mode.') |
| prompt = '# ' |
| self.wait_for_console_pattern(prompt) |
| |
| # Synchronize on virtio-block driver creating the root device |
| exec_command_and_wait_for_pattern(self, |
| "while ! (dmesg -c | grep vda:) ; do sleep 1 ; done", |
| "vda1") |
| |
| exec_command_and_wait_for_pattern(self, 'mount /dev/vda1 /sysroot', |
| prompt) |
| exec_command_and_wait_for_pattern(self, 'chroot /sysroot', |
| prompt) |
| exec_command_and_wait_for_pattern(self, "modprobe virtio-balloon", |
| prompt) |
| |
| def assert_initial_stats(self): |
| ret = self.vm.qmp('qom-get', |
| {'path': '/machine/peripheral/balloon', |
| 'property': 'guest-stats'})['return'] |
| when = ret.get('last-update') |
| assert when == 0 |
| stats = ret.get('stats') |
| for name, val in stats.items(): |
| assert val == UNSET_STATS_VALUE |
| |
| def assert_running_stats(self, then): |
| # We told the QEMU to refresh stats every 100ms, but |
| # there can be a delay between virtio-ballon driver |
| # being modprobed and seeing the first stats refresh |
| # Retry a few times for robustness under heavy load |
| retries = 10 |
| when = 0 |
| while when == 0 and retries: |
| ret = self.vm.qmp('qom-get', |
| {'path': '/machine/peripheral/balloon', |
| 'property': 'guest-stats'})['return'] |
| when = ret.get('last-update') |
| if when == 0: |
| retries = retries - 1 |
| time.sleep(0.5) |
| |
| now = time.time() |
| |
| assert when > then and when < now |
| stats = ret.get('stats') |
| # Stat we expect this particular Kernel to have set |
| expectData = [ |
| "stat-available-memory", |
| "stat-disk-caches", |
| "stat-free-memory", |
| "stat-htlb-pgalloc", |
| "stat-htlb-pgfail", |
| "stat-major-faults", |
| "stat-minor-faults", |
| "stat-swap-in", |
| "stat-swap-out", |
| "stat-total-memory", |
| ] |
| for name, val in stats.items(): |
| if name in expectData: |
| assert val != UNSET_STATS_VALUE |
| else: |
| assert val == UNSET_STATS_VALUE |
| |
| def test_virtio_balloon_stats(self): |
| self.set_machine('q35') |
| self.require_accelerator("kvm") |
| kernel_path = self.ASSET_KERNEL.fetch() |
| initrd_path = self.ASSET_INITRD.fetch() |
| diskimage_path = self.ASSET_DISKIMAGE.fetch() |
| |
| self.vm.set_console() |
| self.vm.add_args("-S") |
| self.vm.add_args("-cpu", "max") |
| self.vm.add_args("-m", "2G") |
| # Slow down BIOS phase with boot menu, so that after a system |
| # reset, we can reliably catch the clean stats again in BIOS |
| # phase before the guest OS launches |
| self.vm.add_args("-boot", "menu=on") |
| self.vm.add_args("-accel", "kvm") |
| self.vm.add_args("-device", "virtio-balloon,id=balloon") |
| self.vm.add_args('-drive', |
| f'file={diskimage_path},if=none,id=drv0,snapshot=on') |
| self.vm.add_args('-device', 'virtio-blk-pci,bus=pcie.0,' + |
| 'drive=drv0,id=virtio-disk0,bootindex=1') |
| |
| self.vm.add_args( |
| "-kernel", |
| kernel_path, |
| "-initrd", |
| initrd_path, |
| "-append", |
| self.DEFAULT_KERNEL_PARAMS |
| ) |
| self.vm.launch() |
| |
| # Poll stats at 100ms |
| self.vm.qmp('qom-set', |
| {'path': '/machine/peripheral/balloon', |
| 'property': 'guest-stats-polling-interval', |
| 'value': 100 }) |
| |
| # We've not run any guest code yet, neither BIOS or guest, |
| # so stats should be all default values |
| self.assert_initial_stats() |
| |
| self.vm.qmp('cont') |
| |
| then = time.time() |
| self.mount_root() |
| self.assert_running_stats(then) |
| |
| # Race window between these two commands, where we |
| # rely on '-boot menu=on' to (hopefully) ensure we're |
| # still executing the BIOS when QEMU processes the |
| # 'stop', and thus have not loaded the virtio-balloon |
| # driver in the guest |
| self.vm.qmp('system_reset') |
| self.vm.qmp('stop') |
| |
| # If the above assumption held, we're in BIOS now and |
| # stats should be all back at their default values |
| self.assert_initial_stats() |
| self.vm.qmp('cont') |
| |
| then = time.time() |
| self.mount_root() |
| self.assert_running_stats(then) |
| |
| |
| if __name__ == '__main__': |
| QemuSystemTest.main() |