version_0.py revision ba1fa66c641651bcf0d058e9f5af2819c2d2690a
1import re, os
2
3from autotest_lib.client.common_lib import utils as common_utils
4from autotest_lib.tko import utils as tko_utils, models, status_lib
5from autotest_lib.tko.parsers import base
6
7
8class job(models.job):
9    def __init__(self, dir):
10        job_dict = job.load_from_dir(dir)
11        super(job, self).__init__(dir, **job_dict)
12
13    @staticmethod
14    def load_from_dir(dir):
15        try:
16            keyval = common_utils.read_keyval(dir)
17            tko_utils.dprint(str(keyval))
18        except Exception:
19            keyval = {}
20
21        user = keyval.get("user", None)
22        label = keyval.get("label", None)
23        machine = keyval.get("hostname", None)
24        if machine:
25            assert "," not in machine
26        queued_time = tko_utils.get_timestamp(keyval, "job_queued")
27        started_time = tko_utils.get_timestamp(keyval, "job_started")
28        finished_time = tko_utils.get_timestamp(keyval, "job_finished")
29        machine_owner = keyval.get("owner", None)
30
31        if not machine:
32            machine = job.find_hostname(dir)
33        tko_utils.dprint("MACHINE NAME: %s" % machine)
34
35        return {"user": user, "label": label, "machine": machine,
36                "queued_time": queued_time,
37                "started_time": started_time,
38                "finished_time": finished_time,
39                "machine_owner": machine_owner}
40
41
42    @staticmethod
43    def find_hostname(path):
44        hostname = os.path.join(path, "sysinfo", "hostname")
45        try:
46            machine = open(hostname).readline().rstrip()
47            return machine
48        except Exception:
49            tko_utils.dprint("Could not read a hostname from "
50                             "sysinfo/hostname")
51
52        uname = os.path.join(path, "sysinfo", "uname_-a")
53        try:
54            machine = open(uname).readline().split()[1]
55            return
56        except Exception:
57            tko_utils.dprint("Could not read a hostname from "
58                             "sysinfo/uname_-a")
59
60        raise Exception("Unable to find a machine name")
61
62
63class kernel(models.kernel):
64    def __init__(self, job, verify_ident=None):
65        kernel_dict = kernel.load_from_dir(job.dir, verify_ident)
66        super(kernel, self).__init__(**kernel_dict)
67
68
69    @staticmethod
70    def load_from_dir(dir, verify_ident=None):
71        # try and load the booted kernel version
72        build_log = os.path.join(dir, "build", "debug", "build_log")
73        attributes = kernel.load_from_build_log(build_log)
74        if not attributes:
75            if verify_ident:
76                base = verify_ident
77            else:
78                base = kernel.load_from_sysinfo(dir)
79            patches = []
80            hashes = []
81        else:
82            base, patches, hashes = attributes
83        tko_utils.dprint("kernel.__init__() found kernel version %s"
84                         % base)
85
86        # compute the kernel hash
87        if base == "UNKNOWN":
88            kernel_hash = "UNKNOWN"
89        else:
90            kernel_hash = kernel.compute_hash(base, hashes)
91
92        return {"base": base, "patches": patches,
93                "kernel_hash": kernel_hash}
94
95
96    @staticmethod
97    def load_from_sysinfo(path):
98        for subdir in ("reboot1", ""):
99            uname_path = os.path.join(path, "sysinfo", subdir,
100                                      "uname_-a")
101            if not os.path.exists(uname_path):
102                continue
103            uname = open(uname_path).readline().split()
104            return re.sub("-autotest$", "", uname[2])
105        return "UNKNOWN"
106
107
108    @staticmethod
109    def load_from_build_log(path):
110        if not os.path.exists(path):
111            return None
112
113        base, patches, hashes = "UNKNOWN", [], []
114        for line in file(path):
115            head, rest = line.split(": ", 1)
116            rest = rest.split()
117            if head == "BASE":
118                base = rest[0]
119            elif head == "PATCH":
120                patches.append(patch(*rest))
121                hashes.append(rest[2])
122        return base, patches, hashes
123
124
125class test(models.test):
126    def __init__(self, subdir, testname, status, reason, test_kernel,
127                 machine, started_time, finished_time, iterations,
128                 attributes):
129        # for backwards compatibility with the original parser
130        # implementation, if there is no test version we need a NULL
131        # value to be used; also, if there is a version it should
132        # be terminated by a newline
133        if "version" in attributes:
134            attributes["version"] = str(attributes["version"])
135        else:
136            attributes["version"] = None
137
138        super(test, self).__init__(subdir, testname, status, reason,
139                                   test_kernel, machine, started_time,
140                                   finished_time, iterations,
141                                   attributes)
142
143
144    @staticmethod
145    def load_iterations(keyval_path):
146        return iteration.load_from_keyval(keyval_path)
147
148
149class patch(models.patch):
150    def __init__(self, spec, reference, hash):
151        tko_utils.dprint("PATCH::%s %s %s" % (spec, reference, hash))
152        super(patch, self).__init__(spec, reference, hash)
153        self.spec = spec
154        self.reference = reference
155        self.hash = hash
156
157
158class iteration(models.iteration):
159    @staticmethod
160    def parse_line_into_dicts(line, attr_dict, perf_dict):
161        key, value = line.split("=", 1)
162        perf_dict[key] = value
163
164
165class status_line(object):
166    def __init__(self, indent, status, subdir, testname, reason,
167                 optional_fields):
168        # pull out the type & status of the line
169        if status == "START":
170            self.type = "START"
171            self.status = None
172        elif status.startswith("END "):
173            self.type = "END"
174            self.status = status[4:]
175        else:
176            self.type = "STATUS"
177            self.status = status
178        assert (self.status is None or
179                self.status in status_lib.statuses)
180
181        # save all the other parameters
182        self.indent = indent
183        self.subdir = self.parse_name(subdir)
184        self.testname = self.parse_name(testname)
185        self.reason = reason
186        self.optional_fields = optional_fields
187
188
189    @staticmethod
190    def parse_name(name):
191        if name == "----":
192            return None
193        return name
194
195
196    @staticmethod
197    def is_status_line(line):
198        return re.search(r"^\t*\S", line) is not None
199
200
201    @classmethod
202    def parse_line(cls, line):
203        if not status_line.is_status_line(line):
204            return None
205        indent, line = re.search(r"^(\t*)(.*)$", line).groups()
206        indent = len(indent)
207
208        # split the line into the fixed and optional fields
209        parts = line.split("\t")
210        status, subdir, testname = parts[0:3]
211        reason = parts[-1]
212        optional_parts = parts[3:-1]
213
214        # all the optional parts should be of the form "key=value"
215        assert sum('=' not in part for part in optional_parts) == 0
216        optional_fields = dict(part.split("=", 1)
217                               for part in optional_parts)
218
219        # build up a new status_line and return it
220        return cls(indent, status, subdir, testname, reason,
221                   optional_fields)
222
223
224class parser(base.parser):
225    @staticmethod
226    def make_job(dir):
227        return job(dir)
228
229
230    def state_iterator(self, buffer):
231        new_tests = []
232        boot_count = 0
233        group_subdir = None
234        sought_level = 0
235        stack = status_lib.status_stack()
236        current_kernel = kernel(self.job)
237        boot_in_progress = False
238        alert_pending = None
239        started_time = None
240
241        while not self.finished or buffer.size():
242            # stop processing once the buffer is empty
243            if buffer.size() == 0:
244                yield new_tests
245                new_tests = []
246                continue
247
248            # parse the next line
249            line = buffer.get()
250            tko_utils.dprint('\nSTATUS: ' + line.strip())
251            line = status_line.parse_line(line)
252            if line is None:
253                tko_utils.dprint('non-status line, ignoring')
254                continue # ignore non-status lines
255
256            # have we hit the job start line?
257            if (line.type == "START" and not line.subdir and
258                not line.testname):
259                sought_level = 1
260                tko_utils.dprint("found job level start "
261                                 "marker, looking for level "
262                                 "1 groups now")
263                continue
264
265            # have we hit the job end line?
266            if (line.type == "END" and not line.subdir and
267                not line.testname):
268                tko_utils.dprint("found job level end "
269                                 "marker, looking for level "
270                                 "0 lines now")
271                sought_level = 0
272
273            # START line, just push another layer on to the stack
274            # and grab the start time if this is at the job level
275            # we're currently seeking
276            if line.type == "START":
277                group_subdir = None
278                stack.start()
279                if line.indent == sought_level:
280                    started_time = \
281                                 tko_utils.get_timestamp(
282                        line.optional_fields, "timestamp")
283                tko_utils.dprint("start line, ignoring")
284                continue
285            # otherwise, update the status on the stack
286            else:
287                tko_utils.dprint("GROPE_STATUS: %s" %
288                                 [stack.current_status(),
289                                  line.status, line.subdir,
290                                  line.testname, line.reason])
291                stack.update(line.status)
292
293            if line.status == "ALERT":
294                tko_utils.dprint("job level alert, recording")
295                alert_pending = line.reason
296                continue
297
298            # ignore Autotest.install => GOOD lines
299            if (line.testname == "Autotest.install" and
300                line.status == "GOOD"):
301                tko_utils.dprint("Successful Autotest "
302                                 "install, ignoring")
303                continue
304
305            # ignore END lines for a reboot group
306            if (line.testname == "reboot" and line.type == "END"):
307                tko_utils.dprint("reboot group, ignoring")
308                continue
309
310            # convert job-level ABORTs into a 'JOB' test, and
311            # ignore other job-level events
312            if line.testname is None:
313                if (line.status == "ABORT" and
314                    line.type != "END"):
315                    line.testname = "JOB"
316                else:
317                    tko_utils.dprint("job level event, "
318                                    "ignoring")
319                    continue
320
321            # use the group subdir for END lines
322            if line.type == "END":
323                line.subdir = group_subdir
324
325            # are we inside a block group?
326            if (line.indent != sought_level and
327                line.status != "ABORT" and
328                not line.testname.startswith('reboot.')):
329                if line.subdir:
330                    tko_utils.dprint("set group_subdir: "
331                                     + line.subdir)
332                    group_subdir = line.subdir
333                tko_utils.dprint("ignoring incorrect indent "
334                                 "level %d != %d," %
335                                 (line.indent, sought_level))
336                continue
337
338            # use the subdir as the testname, except for
339            # boot.* and kernel.* tests
340            if (line.testname is None or
341                not re.search(r"^(boot(\.\d+)?$|kernel\.)",
342                              line.testname)):
343                if line.subdir and '.' in line.subdir:
344                    line.testname = line.subdir
345
346            # has a reboot started?
347            if line.testname == "reboot.start":
348                started_time = tko_utils.get_timestamp(
349                    line.optional_fields, "timestamp")
350                tko_utils.dprint("reboot start event, "
351                                 "ignoring")
352                boot_in_progress = True
353                continue
354
355            # has a reboot finished?
356            if line.testname == "reboot.verify":
357                line.testname = "boot.%d" % boot_count
358                tko_utils.dprint("reboot verified")
359                boot_in_progress = False
360                verify_ident = line.reason.strip()
361                current_kernel = kernel(self.job, verify_ident)
362                boot_count += 1
363
364            if alert_pending:
365                line.status = "ALERT"
366                line.reason = alert_pending
367                alert_pending = None
368
369            # create the actual test object
370            finished_time = tko_utils.get_timestamp(
371                line.optional_fields, "timestamp")
372            final_status = stack.end()
373            tko_utils.dprint("Adding: "
374                             "%s\nSubdir:%s\nTestname:%s\n%s" %
375                             (final_status, line.subdir,
376                              line.testname, line.reason))
377            new_test = test.parse_test(self.job, line.subdir,
378                                       line.testname,
379                                       final_status, line.reason,
380                                       current_kernel,
381                                       started_time,
382                                       finished_time)
383            started_time = None
384            new_tests.append(new_test)
385
386        # the job is finished, but we never came back from reboot
387        if boot_in_progress:
388            testname = "boot.%d" % boot_count
389            reason = "machine did not return from reboot"
390            tko_utils.dprint(("Adding: ABORT\nSubdir:----\n"
391                              "Testname:%s\n%s")
392                             % (testname, reason))
393            new_test = test.parse_test(self.job, None, testname,
394                                       "ABORT", reason,
395                                       current_kernel, None, None)
396            new_tests.append(new_test)
397        yield new_tests
398