version_0.py revision d8561576404bcb7c3c56d1c691129ba285693d03
1import os 2import re 3 4import common 5from autotest_lib.tko import models 6from autotest_lib.tko import status_lib 7from autotest_lib.tko import utils as tko_utils 8from autotest_lib.tko.parsers import base 9 10class NoHostnameError(Exception): 11 pass 12 13 14class BoardLabelError(Exception): 15 pass 16 17 18class job(models.job): 19 def __init__(self, dir): 20 job_dict = job.load_from_dir(dir) 21 super(job, self).__init__(dir, **job_dict) 22 23 24 @classmethod 25 def load_from_dir(cls, dir): 26 keyval = cls.read_keyval(dir) 27 tko_utils.dprint(str(keyval)) 28 29 user = keyval.get("user", None) 30 label = keyval.get("label", None) 31 queued_time = tko_utils.get_timestamp(keyval, "job_queued") 32 started_time = tko_utils.get_timestamp(keyval, "job_started") 33 finished_time = tko_utils.get_timestamp(keyval, "job_finished") 34 machine = cls.determine_hostname(keyval, dir) 35 machine_group = cls.determine_machine_group(machine, dir) 36 machine_owner = keyval.get("owner", None) 37 38 aborted_by = keyval.get("aborted_by", None) 39 aborted_at = tko_utils.get_timestamp(keyval, "aborted_on") 40 41 return {"user": user, "label": label, "machine": machine, 42 "queued_time": queued_time, "started_time": started_time, 43 "finished_time": finished_time, "machine_owner": machine_owner, 44 "machine_group": machine_group, "aborted_by": aborted_by, 45 "aborted_on": aborted_at, "keyval_dict": keyval} 46 47 48 @classmethod 49 def determine_hostname(cls, keyval, job_dir): 50 host_group_name = keyval.get("host_group_name", None) 51 machine = keyval.get("hostname", "") 52 is_multimachine = "," in machine 53 54 # determine what hostname to use 55 if host_group_name: 56 if is_multimachine or not machine: 57 tko_utils.dprint("Using host_group_name %r instead of " 58 "machine name." % host_group_name) 59 machine = host_group_name 60 elif is_multimachine: 61 try: 62 machine = job.find_hostname(job_dir) # find a unique hostname 63 except NoHostnameError: 64 pass # just use the comma-separated name 65 66 tko_utils.dprint("MACHINE NAME: %s" % machine) 67 return machine 68 69 70 @classmethod 71 def determine_machine_group(cls, hostname, job_dir): 72 machine_groups = set() 73 for individual_hostname in hostname.split(","): 74 host_keyval = models.test.parse_host_keyval(job_dir, 75 individual_hostname) 76 if not host_keyval: 77 tko_utils.dprint('Unable to parse host keyval for %s' 78 % individual_hostname) 79 elif 'labels' in host_keyval: 80 # Use board label as machine group. This is to avoid the 81 # confusion of multiple boards mapping to the same platform in 82 # wmatrix. With this change, wmatrix will group tests with the 83 # same board, rather than the same platform. 84 labels = host_keyval['labels'].split(',') 85 board_labels = [l[8:] for l in labels 86 if l.startswith('board%3A')] 87 if board_labels: 88 # Testbeds have multiple boards so concat them into a 89 # single string then add it to the machine_groups list. 90 machine_groups.add(','.join(board_labels)) 91 else: 92 error = ('Failed to retrieve board label from host labels: ' 93 '%s' % host_keyval['labels']) 94 tko_utils.dprint(error) 95 raise BoardLabelError(error) 96 elif "platform" in host_keyval: 97 machine_groups.add(host_keyval["platform"]) 98 machine_group = ",".join(sorted(machine_groups)) 99 tko_utils.dprint("MACHINE GROUP: %s" % machine_group) 100 return machine_group 101 102 103 @staticmethod 104 def find_hostname(path): 105 hostname = os.path.join(path, "sysinfo", "hostname") 106 try: 107 machine = open(hostname).readline().rstrip() 108 return machine 109 except Exception: 110 tko_utils.dprint("Could not read a hostname from " 111 "sysinfo/hostname") 112 113 uname = os.path.join(path, "sysinfo", "uname_-a") 114 try: 115 machine = open(uname).readline().split()[1] 116 return machine 117 except Exception: 118 tko_utils.dprint("Could not read a hostname from " 119 "sysinfo/uname_-a") 120 121 raise NoHostnameError("Unable to find a machine name") 122 123 124class kernel(models.kernel): 125 def __init__(self, job, verify_ident=None): 126 kernel_dict = kernel.load_from_dir(job.dir, verify_ident) 127 super(kernel, self).__init__(**kernel_dict) 128 129 130 @staticmethod 131 def load_from_dir(dir, verify_ident=None): 132 # try and load the booted kernel version 133 attributes = False 134 i = 1 135 build_dir = os.path.join(dir, "build") 136 while True: 137 if not os.path.exists(build_dir): 138 break 139 build_log = os.path.join(build_dir, "debug", "build_log") 140 attributes = kernel.load_from_build_log(build_log) 141 if attributes: 142 break 143 i += 1 144 build_dir = os.path.join(dir, "build.%d" % (i)) 145 146 if not attributes: 147 if verify_ident: 148 base = verify_ident 149 else: 150 base = kernel.load_from_sysinfo(dir) 151 patches = [] 152 hashes = [] 153 else: 154 base, patches, hashes = attributes 155 tko_utils.dprint("kernel.__init__() found kernel version %s" 156 % base) 157 158 # compute the kernel hash 159 if base == "UNKNOWN": 160 kernel_hash = "UNKNOWN" 161 else: 162 kernel_hash = kernel.compute_hash(base, hashes) 163 164 return {"base": base, "patches": patches, 165 "kernel_hash": kernel_hash} 166 167 168 @staticmethod 169 def load_from_sysinfo(path): 170 for subdir in ("reboot1", ""): 171 uname_path = os.path.join(path, "sysinfo", subdir, 172 "uname_-a") 173 if not os.path.exists(uname_path): 174 continue 175 uname = open(uname_path).readline().split() 176 return re.sub("-autotest$", "", uname[2]) 177 return "UNKNOWN" 178 179 180 @staticmethod 181 def load_from_build_log(path): 182 if not os.path.exists(path): 183 return None 184 185 base, patches, hashes = "UNKNOWN", [], [] 186 for line in file(path): 187 head, rest = line.split(": ", 1) 188 rest = rest.split() 189 if head == "BASE": 190 base = rest[0] 191 elif head == "PATCH": 192 patches.append(patch(*rest)) 193 hashes.append(rest[2]) 194 return base, patches, hashes 195 196 197class test(models.test): 198 def __init__(self, subdir, testname, status, reason, test_kernel, 199 machine, started_time, finished_time, iterations, 200 attributes, labels): 201 # for backwards compatibility with the original parser 202 # implementation, if there is no test version we need a NULL 203 # value to be used; also, if there is a version it should 204 # be terminated by a newline 205 if "version" in attributes: 206 attributes["version"] = str(attributes["version"]) 207 else: 208 attributes["version"] = None 209 210 super(test, self).__init__(subdir, testname, status, reason, 211 test_kernel, machine, started_time, 212 finished_time, iterations, 213 attributes, labels) 214 215 216 @staticmethod 217 def load_iterations(keyval_path): 218 return iteration.load_from_keyval(keyval_path) 219 220 221class patch(models.patch): 222 def __init__(self, spec, reference, hash): 223 tko_utils.dprint("PATCH::%s %s %s" % (spec, reference, hash)) 224 super(patch, self).__init__(spec, reference, hash) 225 self.spec = spec 226 self.reference = reference 227 self.hash = hash 228 229 230class iteration(models.iteration): 231 @staticmethod 232 def parse_line_into_dicts(line, attr_dict, perf_dict): 233 key, value = line.split("=", 1) 234 perf_dict[key] = value 235 236 237class status_line(object): 238 def __init__(self, indent, status, subdir, testname, reason, 239 optional_fields): 240 # pull out the type & status of the line 241 if status == "START": 242 self.type = "START" 243 self.status = None 244 elif status.startswith("END "): 245 self.type = "END" 246 self.status = status[4:] 247 else: 248 self.type = "STATUS" 249 self.status = status 250 assert (self.status is None or 251 self.status in status_lib.statuses) 252 253 # save all the other parameters 254 self.indent = indent 255 self.subdir = self.parse_name(subdir) 256 self.testname = self.parse_name(testname) 257 self.reason = reason 258 self.optional_fields = optional_fields 259 260 261 @staticmethod 262 def parse_name(name): 263 if name == "----": 264 return None 265 return name 266 267 268 @staticmethod 269 def is_status_line(line): 270 return re.search(r"^\t*(\S[^\t]*\t){3}", line) is not None 271 272 273 @classmethod 274 def parse_line(cls, line): 275 if not status_line.is_status_line(line): 276 return None 277 match = re.search(r"^(\t*)(.*)$", line, flags=re.DOTALL) 278 if not match: 279 # A more useful error message than: 280 # AttributeError: 'NoneType' object has no attribute 'groups' 281 # to help us debug WTF happens on occasion here. 282 raise RuntimeError("line %r could not be parsed." % line) 283 indent, line = match.groups() 284 indent = len(indent) 285 286 # split the line into the fixed and optional fields 287 parts = line.rstrip("\n").split("\t") 288 289 part_index = 3 290 status, subdir, testname = parts[0:part_index] 291 292 # all optional parts should be of the form "key=value". once we've found 293 # a non-matching part, treat it and the rest of the parts as the reason. 294 optional_fields = {} 295 while part_index < len(parts): 296 kv = re.search(r"^(\w+)=(.+)", parts[part_index]) 297 if not kv: 298 break 299 300 optional_fields[kv.group(1)] = kv.group(2) 301 part_index += 1 302 303 reason = "\t".join(parts[part_index:]) 304 305 # build up a new status_line and return it 306 return cls(indent, status, subdir, testname, reason, 307 optional_fields) 308 309 310class parser(base.parser): 311 @staticmethod 312 def make_job(dir): 313 return job(dir) 314 315 316 def state_iterator(self, buffer): 317 new_tests = [] 318 boot_count = 0 319 group_subdir = None 320 sought_level = 0 321 stack = status_lib.status_stack() 322 current_kernel = kernel(self.job) 323 boot_in_progress = False 324 alert_pending = None 325 started_time = None 326 327 while not self.finished or buffer.size(): 328 # stop processing once the buffer is empty 329 if buffer.size() == 0: 330 yield new_tests 331 new_tests = [] 332 continue 333 334 # parse the next line 335 line = buffer.get() 336 tko_utils.dprint('\nSTATUS: ' + line.strip()) 337 line = status_line.parse_line(line) 338 if line is None: 339 tko_utils.dprint('non-status line, ignoring') 340 continue # ignore non-status lines 341 342 # have we hit the job start line? 343 if (line.type == "START" and not line.subdir and 344 not line.testname): 345 sought_level = 1 346 tko_utils.dprint("found job level start " 347 "marker, looking for level " 348 "1 groups now") 349 continue 350 351 # have we hit the job end line? 352 if (line.type == "END" and not line.subdir and 353 not line.testname): 354 tko_utils.dprint("found job level end " 355 "marker, looking for level " 356 "0 lines now") 357 sought_level = 0 358 359 # START line, just push another layer on to the stack 360 # and grab the start time if this is at the job level 361 # we're currently seeking 362 if line.type == "START": 363 group_subdir = None 364 stack.start() 365 if line.indent == sought_level: 366 started_time = \ 367 tko_utils.get_timestamp( 368 line.optional_fields, "timestamp") 369 tko_utils.dprint("start line, ignoring") 370 continue 371 # otherwise, update the status on the stack 372 else: 373 tko_utils.dprint("GROPE_STATUS: %s" % 374 [stack.current_status(), 375 line.status, line.subdir, 376 line.testname, line.reason]) 377 stack.update(line.status) 378 379 if line.status == "ALERT": 380 tko_utils.dprint("job level alert, recording") 381 alert_pending = line.reason 382 continue 383 384 # ignore Autotest.install => GOOD lines 385 if (line.testname == "Autotest.install" and 386 line.status == "GOOD"): 387 tko_utils.dprint("Successful Autotest " 388 "install, ignoring") 389 continue 390 391 # ignore END lines for a reboot group 392 if (line.testname == "reboot" and line.type == "END"): 393 tko_utils.dprint("reboot group, ignoring") 394 continue 395 396 # convert job-level ABORTs into a 'CLIENT_JOB' test, and 397 # ignore other job-level events 398 if line.testname is None: 399 if (line.status == "ABORT" and 400 line.type != "END"): 401 line.testname = "CLIENT_JOB" 402 else: 403 tko_utils.dprint("job level event, " 404 "ignoring") 405 continue 406 407 # use the group subdir for END lines 408 if line.type == "END": 409 line.subdir = group_subdir 410 411 # are we inside a block group? 412 if (line.indent != sought_level and 413 line.status != "ABORT" and 414 not line.testname.startswith('reboot.')): 415 if line.subdir: 416 tko_utils.dprint("set group_subdir: " 417 + line.subdir) 418 group_subdir = line.subdir 419 tko_utils.dprint("ignoring incorrect indent " 420 "level %d != %d," % 421 (line.indent, sought_level)) 422 continue 423 424 # use the subdir as the testname, except for 425 # boot.* and kernel.* tests 426 if (line.testname is None or 427 not re.search(r"^(boot(\.\d+)?$|kernel\.)", 428 line.testname)): 429 if line.subdir and '.' in line.subdir: 430 line.testname = line.subdir 431 432 # has a reboot started? 433 if line.testname == "reboot.start": 434 started_time = tko_utils.get_timestamp( 435 line.optional_fields, "timestamp") 436 tko_utils.dprint("reboot start event, " 437 "ignoring") 438 boot_in_progress = True 439 continue 440 441 # has a reboot finished? 442 if line.testname == "reboot.verify": 443 line.testname = "boot.%d" % boot_count 444 tko_utils.dprint("reboot verified") 445 boot_in_progress = False 446 verify_ident = line.reason.strip() 447 current_kernel = kernel(self.job, verify_ident) 448 boot_count += 1 449 450 if alert_pending: 451 line.status = "ALERT" 452 line.reason = alert_pending 453 alert_pending = None 454 455 # create the actual test object 456 finished_time = tko_utils.get_timestamp( 457 line.optional_fields, "timestamp") 458 final_status = stack.end() 459 tko_utils.dprint("Adding: " 460 "%s\nSubdir:%s\nTestname:%s\n%s" % 461 (final_status, line.subdir, 462 line.testname, line.reason)) 463 new_test = test.parse_test(self.job, line.subdir, 464 line.testname, 465 final_status, line.reason, 466 current_kernel, 467 started_time, 468 finished_time) 469 started_time = None 470 new_tests.append(new_test) 471 472 # the job is finished, but we never came back from reboot 473 if boot_in_progress: 474 testname = "boot.%d" % boot_count 475 reason = "machine did not return from reboot" 476 tko_utils.dprint(("Adding: ABORT\nSubdir:----\n" 477 "Testname:%s\n%s") 478 % (testname, reason)) 479 new_test = test.parse_test(self.job, None, testname, 480 "ABORT", reason, 481 current_kernel, None, None) 482 new_tests.append(new_test) 483 yield new_tests 484