balloon_check.py 13.9 KB
Newer Older
1
import time
L
Lucas Meneghel Rodrigues 已提交
2 3 4
import re
import logging
import random
5
from autotest.client.shared import error
6 7 8
from virttest import qemu_monitor
from virttest import utils_test
from virttest import utils_misc
9
from virttest.utils_test.qemu import MemoryBaseTest
10 11


12
class BallooningTest(MemoryBaseTest):
L
Lucas Meneghel Rodrigues 已提交
13

14
    """
Y
Yiqiao Pu 已提交
15
    Provide basic functions for memory ballooning test cases
16
    """
L
Lucas Meneghel Rodrigues 已提交
17

Y
Yiqiao Pu 已提交
18
    def __init__(self, test, params, env):
19
        self.test_round = 0
20
        self.ratio = float(params.get("ratio", 0.1))
21
        super(BallooningTest, self).__init__(test, params, env)
22

Y
Yiqiao Pu 已提交
23
        self.vm = env.get_vm(params["main_vm"])
24 25
        self.session = self.get_session(self.vm)
        self.ori_mem = self.get_vm_mem(self.vm)
Y
Yiqiao Pu 已提交
26 27 28 29 30 31 32 33
        self.current_mmem = self.get_ballooned_memory()
        if self.current_mmem != self.ori_mem:
            self.balloon_memory(self.ori_mem)
        self.ori_gmem = self.get_memory_status()
        self.current_gmem = self.ori_gmem
        self.current_mmem = self.ori_mem

    def get_ballooned_memory(self):
34
        """
Y
Yiqiao Pu 已提交
35
        Get the size of memory from monitor
36

Y
Yiqiao Pu 已提交
37 38
        :return: the size of memory
        :rtype: int
39 40
        """
        try:
Y
Yiqiao Pu 已提交
41 42 43
            output = self.vm.monitor.info("balloon")
            ballooned_mem = int(re.findall(r"\d+", str(output))[0])
            if self.vm.monitor.protocol == "qmp":
44 45
                ballooned_mem = ballooned_mem / (1024 ** 2)
        except qemu_monitor.MonitorError as emsg:
Y
Yiqiao Pu 已提交
46
            logging.error(emsg)
47
            return 0
Y
Yiqiao Pu 已提交
48
        return ballooned_mem
49

Y
Yiqiao Pu 已提交
50 51
    @error.context_aware
    def memory_check(self, step, ballooned_mem):
52 53
        """
        Check memory status according expect values
54

L
Lucas Meneghel Rodrigues 已提交
55
        :param step: the check point string
Y
Yiqiao Pu 已提交
56
        :type step: string
L
Lucas Meneghel Rodrigues 已提交
57
        :param ballooned_mem: ballooned memory in current step
Y
Yiqiao Pu 已提交
58 59 60
        :type ballooned_mem: int
        :return: memory size get from monitor and guest
        :rtype: tuple
61 62
        """
        error.context("Check memory status %s" % step, logging.info)
Y
Yiqiao Pu 已提交
63 64
        mmem = self.get_ballooned_memory()
        gmem = self.get_memory_status()
65
        guest_ballooned_mem = abs(gmem - self.ori_gmem)
L
Lukáš Doktor 已提交
66
        if (abs(mmem - self.ori_mem) != ballooned_mem or
67
                (abs(guest_ballooned_mem - ballooned_mem) > 100)):
Y
Yiqiao Pu 已提交
68
            self.error_report(step, self.ori_mem - ballooned_mem, mmem, gmem)
69
            raise error.TestFail("Balloon test failed %s" % step)
Y
Yiqiao Pu 已提交
70
        return (mmem, gmem)
71

Y
Yiqiao Pu 已提交
72 73
    @error.context_aware
    def balloon_memory(self, new_mem):
74 75 76 77
        """
        Baloon memory to new_mem and verifies on both qemu monitor and
        guest OS if change worked.

L
Lucas Meneghel Rodrigues 已提交
78
        :param new_mem: New desired memory.
Y
Yiqiao Pu 已提交
79
        :type new_mem: int
80
        """
81
        error.context("Change VM memory to %s" % new_mem, logging.info)
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
        try:
            self.vm.balloon(new_mem)
        except Exception, e:
            if self.params.get('illegal_value_check', 'no') == 'no' and new_mem != self.get_ballooned_memory():
                raise error.TestFail("Balloon memory fail with error message: %s" % e)
        if new_mem > self.ori_mem:
            compare_mem = self.ori_mem
        elif new_mem == 0:
            compare_mem = self.current_mmem
        elif new_mem <= 100:
            time.sleep(60)
            self.current_mmem = self.get_ballooned_memory()
            compare_mem = self.current_mmem
        else:
            compare_mem = new_mem

Y
Yiqiao Pu 已提交
98
        balloon_timeout = float(self.params.get("balloon_timeout", 100))
L
Lukáš Doktor 已提交
99 100
        status = utils_misc.wait_for((lambda: compare_mem ==
                                      self.get_ballooned_memory()),
L
Lucas Meneghel Rodrigues 已提交
101
                                     balloon_timeout)
Y
Yiqiao Pu 已提交
102
        if status is None:
103 104 105
            raise error.TestFail("Failed to balloon memory to expect"
                                 " value during %ss" % balloon_timeout)

Y
Yiqiao Pu 已提交
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
    def run_balloon_sub_test(self, test, params, env, test_tag):
        """
        Run subtest after ballooned memory. Set up the related parameters
        according to the subtest.

        :param test: QEMU test object
        :type test: object
        :param params: Dictionary with the test parameters
        :type param: dict
        :param env: Dictionary with test environment.
        :type env: dict
        :return: if qemu-kvm process quit after test. There are three status
                 for this variable. -1 means the process will not quit. 0
                 means the process will quit but already restart in sub test.
                 1 means the process quit after sub test.
        :rtype: int
        """
        utils_test.run_virt_sub_test(test, params, env,
                                     sub_type=test_tag)
        qemu_quit_after_test = -1
        if "shutdown" in test_tag:
127
            logging.info("Guest shutdown normally after balloon")
Y
Yiqiao Pu 已提交
128
            qemu_quit_after_test = 1
129
        if params.get("session_need_update", "no") == "yes":
Y
Yiqiao Pu 已提交
130 131
            timeout = int(self.params.get("login_timeout", 360))
            self.session = self.vm.wait_for_login(timeout=timeout)
132
        if params.get("qemu_quit_after_sub_case", "no") == "yes":
Y
Yiqiao Pu 已提交
133 134 135 136 137
            self.current_mmem = self.ori_mem
            self.current_gmem = self.ori_gmem
            qemu_quit_after_test = 0
        return qemu_quit_after_test

138
    def get_memory_boundary(self, balloon_type=''):
Y
Yiqiao Pu 已提交
139 140 141 142 143 144 145 146 147
        """
        Get the legal memory boundary for balloon operation.

        :param balloon_type: evict or enlarge
        :type balloon_type: string
        :return: min and max size of the memory
        :rtype: tuple
        """
        max_size = self.ori_mem
148 149
        min_size = self.params.get("minmem", "512M")
        min_size = int(float(utils_misc.normalize_data_size(min_size)))
150 151 152 153 154 155 156 157
        if self.params.get('os_type') == 'windows':
            logging.info("Get windows miminum balloon value:")
            self.vm.balloon(1)
            time.sleep(90)
            used_size = int(self.get_ballooned_memory() + self.ratio * self.ori_mem)
            self.vm.balloon(max_size)
            time.sleep(90)
            self.ori_gmem = self.get_memory_status()
Y
Yiqiao Pu 已提交
158 159
        else:
            vm_total = self.get_memory_status()
160 161
            vm_mem_free = self.get_free_mem()
            used_size = vm_total - vm_mem_free + 16
162 163 164 165 166
        if balloon_type == "enlarge":
            min_size = self.current_mmem
        elif balloon_type == "evict":
            max_size = self.current_mmem
        min_size = max(used_size, min_size)
Y
Yiqiao Pu 已提交
167 168 169 170 171 172 173 174 175 176 177 178 179 180
        return min_size, max_size

    @error.context_aware
    def run_ballooning_test(self, expect_mem, tag):
        """
        Run a loop of ballooning test

        :param expect_mem: memory will be setted in test
        :type expect_mem: int
        :param tag: test tag to get related params
        :type tag: string
        :return: If test should quit after test
        :rtype: bool
        """
181 182 183 184
        def _memory_check_after_sub_test():
            try:
                output = self.memory_check("after subtest", ballooned_mem)
            except error.TestFail:
185
                return None
186 187
            return output

Y
Yiqiao Pu 已提交
188 189 190 191 192 193
        if self.test_round < 1:
            self.memory_check("before ballooning test", 0)

        params_tag = self.params.object_params(tag)
        self.balloon_memory(expect_mem)
        self.test_round += 1
194 195 196 197 198 199
        ballooned_memory = self.ori_mem - expect_mem
        if ballooned_memory < 0 or ballooned_memory == self.ori_mem:
            ballooned_memory = 0
        elif ballooned_memory >= self.ori_mem - 100:
            ballooned_memory = self.ori_mem - self.current_mmem
        mmem, gmem = self.memory_check("after %s memory" % tag, ballooned_memory)
Y
Yiqiao Pu 已提交
200 201
        self.current_mmem = mmem
        self.current_gmem = gmem
L
Lukáš Doktor 已提交
202 203
        if (params_tag.get("run_sub_test_after_balloon", "no") == "yes" and
                params_tag.get('sub_test_after_balloon')):
204
            sub_type = params_tag['sub_test_after_balloon']
Y
Yiqiao Pu 已提交
205
            should_quit = self.run_balloon_sub_test(self.test, params_tag,
206
                                                    self.env, sub_type)
Y
Yiqiao Pu 已提交
207 208
            if should_quit == 1:
                return True
209

Y
Yiqiao Pu 已提交
210 211 212
            elif should_quit == 0:
                expect_mem = self.ori_mem

213 214
            sleep_before_check = int(self.params.get("sleep_before_check", 0))
            timeout = int(self.params.get("balloon_timeout", 100)) + sleep_before_check
215
            ballooned_mem = abs(self.ori_mem - expect_mem)
216 217
            msg = "Wait memory balloon back after "
            msg += params_tag['sub_test_after_balloon']
218
            mmem, gmem = utils_misc.wait_for(_memory_check_after_sub_test,
219
                                             timeout, sleep_before_check, 5, msg)
220 221 222

            self.current_mmem = mmem
            self.current_gmem = gmem
Y
Yiqiao Pu 已提交
223 224 225 226 227 228 229 230 231
        return False

    def reset_memory(self):
        """
        Reset memory to original value
        """
        if self.vm.is_alive():
            self.balloon_memory(self.ori_mem)

232 233 234 235 236 237
    def get_free_mem(self):
        """
        Report free memory detect by OS.
        """
        return self.get_guest_free_mem(self.vm)

238 239 240 241 242 243
    def get_used_mem(self):
        """
        Report used memory detect by OS.
        """
        return self.get_guest_used_mem(self.vm)

244 245 246 247 248 249
    def get_total_mem(self):
        """
        Report total memory detect by OS.
        """
        return self.get_guest_total_mem(self.vm)

Y
Yiqiao Pu 已提交
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270
    def error_report(self, step, expect_value, monitor_value, guest_value):
        """
        Generate the error report

        :param step: the step of the error happen
        :param expect_value: memory size assign to the vm
        :param monitor_value: memory size report from monitor, this value can
                              be None
        :param guest_value: memory size report from guest, this value can be
                            None
        """
        pass

    def get_memory_status(self):
        """
        Get Memory status inside guest.
        """
        pass


class BallooningTestWin(BallooningTest):
L
Lucas Meneghel Rodrigues 已提交
271

Y
Yiqiao Pu 已提交
272 273 274
    """
    Windows memory ballooning test
    """
L
Lucas Meneghel Rodrigues 已提交
275

Y
Yiqiao Pu 已提交
276 277 278 279 280 281 282 283 284 285 286 287
    def error_report(self, step, expect_value, monitor_value, guest_value):
        """
        Generate the error report

        :param step: the step of the error happen
        :param expect_value: memory size assign to the vm
        :param monitor_value: memory size report from monitor, this value can
                              be None
        :param guest_value: memory size report from guest, this value can be
                            None
        """
        logging.error("Memory size mismatch %s:\n" % step)
288 289
        error_msg = "Wanted to be changed: %s\n" % abs(self.ori_mem -
                                                       expect_value)
Y
Yiqiao Pu 已提交
290
        if monitor_value:
291 292 293 294
            error_msg += "Changed in monitor: %s\n" % abs(self.ori_mem -
                                                          monitor_value)
        error_msg += "Changed in guest: %s\n" % abs(
            guest_value - self.ori_gmem)
Y
Yiqiao Pu 已提交
295 296 297 298 299 300
        logging.error(error_msg)

    def get_memory_status(self):
        """
        Get Memory status inside guest.

301
        :return: the used memory size inside guest.
Y
Yiqiao Pu 已提交
302 303
        :rtype: int
        """
304
        return int(self.get_used_mem())
Y
Yiqiao Pu 已提交
305 306 307


class BallooningTestLinux(BallooningTest):
L
Lucas Meneghel Rodrigues 已提交
308

Y
Yiqiao Pu 已提交
309 310 311
    """
    Linux memory ballooning test
    """
L
Lucas Meneghel Rodrigues 已提交
312

Y
Yiqiao Pu 已提交
313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335
    def error_report(self, step, expect_value, monitor_value, guest_value):
        """
        Generate the error report

        @param step: the step of the error happen
        @param expect_value: memory size assign to the vm
        @param monitor_value: memory size report from monitor, this value can
                              be None
        @param guest_value: memory size report from guest, this value can be
                            None
        """
        logging.error("Memory size mismatch %s:\n" % step)
        error_msg = "Assigner to VM: %s\n" % expect_value
        if monitor_value:
            error_msg += "Reported by monitor: %s\n" % monitor_value
        if guest_value:
            error_msg += "Reported by guest OS: %s\n" % guest_value
        logging.error(error_msg)

    def get_memory_status(self):
        """
        Get Memory status inside guest.
        """
336
        return int(self.get_total_mem())
Y
Yiqiao Pu 已提交
337 338 339


@error.context_aware
340
def run(test, params, env):
Y
Yiqiao Pu 已提交
341 342 343 344 345 346 347 348 349 350 351 352 353 354 355
    """
    Check Memory ballooning, use M when compare memory in this script:
    1) Boot a guest with balloon enabled.
    2) Balloon guest memory to given value and run sub test(Optional)
    3) Repeat step 2 following the cfg files.
    8) Reset memory back to the original value

    :param test: QEMU test object
    :param params: Dictionary with the test parameters
    :param env: Dictionary with test environment.
    """
    if params['os_type'] == 'windows':
        balloon_test = BallooningTestWin(test, params, env)
    else:
        balloon_test = BallooningTestLinux(test, params, env)
356 357
        logging.info("Waiting 90s for guest's applications up")
        time.sleep(90)
Y
Yiqiao Pu 已提交
358 359 360 361 362 363 364 365 366

    for tag in params.objects('test_tags'):
        error.context("Running %s test" % tag, logging.info)
        params_tag = params.object_params(tag)
        if params_tag.get('expect_memory'):
            expect_mem = int(params_tag.get('expect_memory'))
        elif params_tag.get('expect_memory_ratio'):
            expect_mem = int(balloon_test.ori_mem *
                             float(params_tag.get('expect_memory_ratio')))
367 368
        elif tag == 'evict' and params_tag.get('illegal_value_check', 'no') == 'yes' and params['os_type'] == 'windows':
            expect_mem = int(random.uniform(0, 100))
Y
Yiqiao Pu 已提交
369 370 371
        else:
            balloon_type = params_tag['balloon_type']
            min_sz, max_sz = balloon_test.get_memory_boundary(balloon_type)
372 373 374 375 376 377 378
            if params_tag.get('illegal_value_check', 'no') == 'yes':
                if tag == 'enlarge':
                    expect_mem = int((max_sz)+random.uniform(1, 1000))
                else:
                    expect_mem = 0
            else:
                expect_mem = int(random.uniform(min_sz, max_sz))
Y
Yiqiao Pu 已提交
379 380 381

        quit_after_test = balloon_test.run_ballooning_test(expect_mem, tag)
        if quit_after_test:
382
            return
383

Y
Yiqiao Pu 已提交
384
    balloon_test.reset_memory()