Home | History | Annotate | Download | only in kernel_MemoryRamoop
      1 # Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 import logging, random, re, string, traceback
      6 from autotest_lib.client.common_lib import error
      7 from autotest_lib.server import autotest
      8 from autotest_lib.server import hosts
      9 from autotest_lib.server import test
     10 
     11 class kernel_MemoryRamoop(test.test):
     12     """
     13     This test verify that /dev/pstore/console-ramoops is preserved correctly
     14     after system reboot/kernel crash and also verify that there is no memory
     15     corrupt in that log.
     16 
     17     There is also platform_KernelErrorPaths test that test kernel crash. But
     18     this test focuses on verify that kernel create console-ramoops file
     19     correctly and its content is not corrupt. Contrary to the other test
     20     that test at the bigger scope, i.e. the whole error reporting mechanism.
     21     """
     22     version = 1
     23 
     24     _RAMOOP_PATH = '/dev/pstore/console-ramoops'
     25     _KMSG_PATH = '/dev/kmsg'
     26     _LKDTM_PATH = '/sys/kernel/debug/provoke-crash/DIRECT'
     27 
     28     # ramopp have size of 128K so we will generate about 100K of random message
     29     _MSG_LINE_COUNT = 1000
     30     _MSG_LINE_LENGTH = 80
     31     _MSG_MAGIC = 'ramoop_test'
     32 
     33     def run_once(self, client_ip):
     34         """
     35         Run the test.
     36         """
     37         if not client_ip:
     38             error.TestError("Must provide client's IP address to test")
     39 
     40         self._client = hosts.create_host(client_ip)
     41         self._client_at = autotest.Autotest(self._client)
     42 
     43         self._run_test(self._do_reboot, '.*Restarting system.*$')
     44 
     45         if self._client.check_for_lkdtm():
     46             self._run_test(self._do_kernel_panic, '.*lkdtm:.*PANIC$')
     47             self._run_test(self._do_kernel_bug, '.*lkdtm:.*BUG$')
     48         else:
     49             logging.warn('DUT did not have kernel dump test module')
     50 
     51         self._run_test(self._do_reboot_with_suspend, '.*Restarting system.*$')
     52 
     53     def _run_test(self, test_function, sig_pattern):
     54         """
     55         Run the test using by write random message to kernel log. Then
     56         restart/crash the kernel and then verify integrity of console-ramoop
     57 
     58         @param test_function: fuction to call to reboot / crash DUT
     59         @param sig_pattern: regex of kernel log message generate when reboot
     60                             or crash by test_function
     61         """
     62 
     63         msg = self._generate_random_msg()
     64 
     65         for line in msg:
     66             cmd = 'echo "%s" > %s' % (line, self._KMSG_PATH)
     67             self._client.run(cmd)
     68 
     69         test_function()
     70 
     71         cmd = 'cat %s' % self._RAMOOP_PATH
     72         ramoop = self._client.run(cmd).stdout
     73 
     74         self._verify_random_msg(ramoop, msg, sig_pattern)
     75 
     76     def _do_reboot(self):
     77         """
     78         Reboot host machine
     79         """
     80         logging.info('Server: reboot client')
     81         try:
     82             self._client.reboot()
     83         except error.AutoservRebootError as err:
     84             raise error.TestFail('%s.\nTest failed with error %s' % (
     85                     traceback.format_exc(), str(err)))
     86 
     87     def _do_reboot_with_suspend(self):
     88         """
     89         Reboot host machine after suspend once
     90         """
     91         self._client.suspend(suspend_time=15)
     92 
     93         logging.info('Server: reboot client')
     94         try:
     95             self._client.reboot()
     96         except error.AutoservRebootError as err:
     97             raise error.TestFail('%s.\nTest failed with error %s' % (
     98                     traceback.format_exc(), str(err)))
     99 
    100     def _do_kernel_panic(self):
    101         """
    102         Cause kernel panic using kernel dump test module
    103         """
    104         logging.info('Server: make client kernel panic')
    105 
    106         cmd = 'echo PANIC > %s' % self._LKDTM_PATH
    107         boot_id = self._client.get_boot_id()
    108         self._client.run(cmd, ignore_status=True)
    109         self._client.wait_for_restart(old_boot_id=boot_id)
    110 
    111     def _do_kernel_bug(self):
    112         """
    113         Cause kernel bug using kernel dump test module
    114         """
    115         logging.info('Server: make client kernel bug')
    116 
    117         cmd = 'echo BUG > %s' % self._LKDTM_PATH
    118         boot_id = self._client.get_boot_id()
    119         self._client.run(cmd, ignore_status=True)
    120         self._client.wait_for_restart(old_boot_id=boot_id)
    121 
    122     def _generate_random_msg(self):
    123         """
    124         Generate random message to put in kernel log
    125         The message format is [magic string]: [3 digit id] [random char/digit]
    126         """
    127         valid_char = string.letters + string.digits
    128         ret = []
    129         for i in range(self._MSG_LINE_COUNT):
    130             line = '%s: %03d ' % (self._MSG_MAGIC, i)
    131             for _ in range(self._MSG_LINE_LENGTH):
    132                 line += random.choice(valid_char)
    133             ret += [line]
    134         return ret
    135 
    136     def _verify_random_msg(self, ramoop, src_msg, sig_pattern):
    137         """
    138         Verify random message generated by _generate_random_msg
    139 
    140         There are 3 things to verify.
    141         1. At least one random message exist. (earlier random message may be
    142            cutoff because console-ramoops has limited size.
    143         2. Integrity of random message.
    144         3. Signature of reboot / kernel crash
    145 
    146         @param ramoop: console-ramoops file in DUT
    147         @param src_msg: message write to kernel log
    148         @param sig_patterm: regex of kernel log to verify
    149         """
    150         #                   time stamp     magic   id      random
    151         pattern = str("\\[ *(\\d+\\.\\d+)\\].*(%s: (\\d{3}) \\w{%d})" %
    152             (self._MSG_MAGIC, self._MSG_LINE_LENGTH))
    153         matcher = re.compile(pattern)
    154 
    155         logging.info('%s', pattern)
    156 
    157         state = 'find_rand_msg'
    158 
    159         last_timestamp = 0
    160         for line in ramoop.split('\n'):
    161             if state == 'find_rand_msg':
    162                 if not matcher.match(line):
    163                     continue
    164                 last_id = int(matcher.split(line)[3]) - 1
    165                 state = 'match_rand_pattern'
    166                 logging.info("%s: %s", state, line)
    167 
    168             if state == 'match_rand_pattern':
    169                 if not matcher.match(line):
    170                     continue
    171                 components = matcher.split(line)
    172                 timestamp = float(components[1])
    173                 msg = components[2]
    174                 id = int(components[3])
    175 
    176                 if timestamp < last_timestamp:
    177                     logging.info("last_timestamp: %f, timestamp: %d",
    178                                  last_timestamp, timestamp)
    179                     raise error.TestFail('Found reverse time stamp.')
    180                 last_timestamp = timestamp
    181 
    182                 if id != last_id + 1:
    183                     logging.info("last_id: %d, id: %d", last_id, id)
    184                     raise error.TestFail('Found missing message.')
    185                 last_id = id
    186 
    187                 if msg != src_msg[id]:
    188                     logging.info("cur_msg: '%s'", msg)
    189                     logging.info("src_msg: '%s'", src_msg[id])
    190                     raise error.TestFail('Found corrupt message.')
    191 
    192                 if id == self._MSG_LINE_COUNT - 1:
    193                     state = 'find_signature'
    194 
    195             if state == 'find_signature':
    196                 if re.match(sig_pattern, line):
    197                     logging.info("%s: %s", state, line)
    198                     break
    199 
    200         # error case: successful run must break in find_sigature state
    201         else:
    202             raise error.TestFail(str('Verify failed at state %s' % state))
    203