xref: /qemu/tests/functional/test_virtio_balloon.py (revision 842721581fdbed45fa4738d02df8d28b1eaf28dd)
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        kernel_path = self.ASSET_KERNEL.fetch()
114        initrd_path = self.ASSET_INITRD.fetch()
115        diskimage_path = self.ASSET_DISKIMAGE.fetch()
116
117        self.vm.set_console()
118        self.vm.add_args("-S")
119        self.vm.add_args("-cpu", "max")
120        self.vm.add_args("-m", "2G")
121        # Slow down BIOS phase with boot menu, so that after a system
122        # reset, we can reliably catch the clean stats again in BIOS
123        # phase before the guest OS launches
124        self.vm.add_args("-boot", "menu=on")
125        self.vm.add_args("-machine", "q35,accel=kvm:tcg")
126        self.vm.add_args("-device", "virtio-balloon,id=balloon")
127        self.vm.add_args('-drive',
128                         f'file={diskimage_path},if=none,id=drv0,snapshot=on')
129        self.vm.add_args('-device', 'virtio-blk-pci,bus=pcie.0,' +
130                         'drive=drv0,id=virtio-disk0,bootindex=1')
131
132        self.vm.add_args(
133            "-kernel",
134            kernel_path,
135            "-initrd",
136            initrd_path,
137            "-append",
138            self.DEFAULT_KERNEL_PARAMS
139        )
140        self.vm.launch()
141
142        # Poll stats at 100ms
143        self.vm.qmp('qom-set',
144                    {'path': '/machine/peripheral/balloon',
145                     'property': 'guest-stats-polling-interval',
146                     'value': 100 })
147
148        # We've not run any guest code yet, neither BIOS or guest,
149        # so stats should be all default values
150        self.assert_initial_stats()
151
152        self.vm.qmp('cont')
153
154        then = time.time()
155        self.mount_root()
156        self.assert_running_stats(then)
157
158        # Race window between these two commands, where we
159        # rely on '-boot menu=on' to (hopefully) ensure we're
160        # still executing the BIOS when QEMU processes the
161        # 'stop', and thus have not loaded the virtio-balloon
162        # driver in the guest
163        self.vm.qmp('system_reset')
164        self.vm.qmp('stop')
165
166        # If the above assumption held, we're in BIOS now and
167        # stats should be all back at their default values
168        self.assert_initial_stats()
169        self.vm.qmp('cont')
170
171        then = time.time()
172        self.mount_root()
173        self.assert_running_stats(then)
174
175
176if __name__ == '__main__':
177    QemuSystemTest.main()
178