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