• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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