blob: feff3071391e5735bdd7ccb4e9fb0632c8f7386d [file] [log] [blame]
mbligh96cf0512008-04-17 15:25:38 +00001#!/usr/bin/python -u
mblighc2514542008-02-19 15:54:26 +00002
mblighb33e53e2008-06-17 19:41:26 +00003import os, sys, optparse, fcntl, errno, traceback, socket
mblighbb7b8912006-10-08 03:59:02 +00004
mbligh96cf0512008-04-17 15:25:38 +00005import common
jadmanskidb4f9b52008-12-03 22:52:53 +00006from autotest_lib.client.common_lib import mail, pidfile
7from autotest_lib.tko import db as tko_db, utils as tko_utils, status_lib, models
mbligh74fc0462007-11-05 20:24:17 +00008
9
mbligh96cf0512008-04-17 15:25:38 +000010def parse_args():
jadmanski0afbb632008-06-06 21:10:57 +000011 # build up our options parser and parse sys.argv
12 parser = optparse.OptionParser()
13 parser.add_option("-m", help="Send mail for FAILED tests",
14 dest="mailit", action="store_true")
15 parser.add_option("-r", help="Reparse the results of a job",
16 dest="reparse", action="store_true")
17 parser.add_option("-o", help="Parse a single results directory",
18 dest="singledir", action="store_true")
19 parser.add_option("-l", help=("Levels of subdirectories to include "
20 "in the job name"),
21 type="int", dest="level", default=1)
22 parser.add_option("-n", help="No blocking on an existing parse",
23 dest="noblock", action="store_true")
24 parser.add_option("-s", help="Database server hostname",
25 dest="db_host", action="store")
26 parser.add_option("-u", help="Database username", dest="db_user",
27 action="store")
28 parser.add_option("-p", help="Database password", dest="db_pass",
29 action="store")
30 parser.add_option("-d", help="Database name", dest="db_name",
31 action="store")
jadmanskid5ab8c52008-12-03 16:27:07 +000032 parser.add_option("--write-pidfile",
33 help="write pidfile (.parser_execute)",
34 dest="write_pidfile", action="store_true",
35 default=False)
jadmanski0afbb632008-06-06 21:10:57 +000036 options, args = parser.parse_args()
mbligh74fc0462007-11-05 20:24:17 +000037
jadmanski0afbb632008-06-06 21:10:57 +000038 # we need a results directory
39 if len(args) == 0:
40 tko_utils.dprint("ERROR: at least one results directory must "
41 "be provided")
42 parser.print_help()
43 sys.exit(1)
mbligh74fc0462007-11-05 20:24:17 +000044
jadmanski0afbb632008-06-06 21:10:57 +000045 # pass the options back
46 return options, args
mbligh74fc0462007-11-05 20:24:17 +000047
48
mbligh96cf0512008-04-17 15:25:38 +000049def format_failure_message(jobname, kernel, testname, status, reason):
jadmanski0afbb632008-06-06 21:10:57 +000050 format_string = "%-12s %-20s %-12s %-10s %s"
51 return format_string % (jobname, kernel, testname, status, reason)
mblighb85e6b02006-10-08 17:20:56 +000052
mblighbb7b8912006-10-08 03:59:02 +000053
mbligh96cf0512008-04-17 15:25:38 +000054def mailfailure(jobname, job, message):
jadmanski0afbb632008-06-06 21:10:57 +000055 message_lines = [""]
56 message_lines.append("The following tests FAILED for this job")
57 message_lines.append("http://%s/results/%s" %
58 (socket.gethostname(), jobname))
59 message_lines.append("")
60 message_lines.append(format_failure_message("Job name", "Kernel",
61 "Test name", "FAIL/WARN",
62 "Failure reason"))
63 message_lines.append(format_failure_message("=" * 8, "=" * 6, "=" * 8,
64 "=" * 8, "=" * 14))
65 message_header = "\n".join(message_lines)
mbligh96cf0512008-04-17 15:25:38 +000066
jadmanski0afbb632008-06-06 21:10:57 +000067 subject = "AUTOTEST: FAILED tests from job %s" % jobname
68 mail.send("", job.user, "", subject, message_header + message)
mbligh006f2302007-09-13 20:46:46 +000069
70
mbligh96cf0512008-04-17 15:25:38 +000071def parse_one(db, jobname, path, reparse, mail_on_failure):
jadmanski0afbb632008-06-06 21:10:57 +000072 """
73 Parse a single job. Optionally send email on failure.
74 """
75 tko_utils.dprint("\nScanning %s (%s)" % (jobname, path))
76 if reparse and db.find_job(jobname):
77 tko_utils.dprint("! Deleting old copy of job results to "
78 "reparse it")
79 db.delete_job(jobname)
80 if db.find_job(jobname):
81 tko_utils.dprint("! Job is already parsed, done")
82 return
mbligh96cf0512008-04-17 15:25:38 +000083
jadmanski0afbb632008-06-06 21:10:57 +000084 # look up the status version
jadmanskidb4f9b52008-12-03 22:52:53 +000085 job_keyval = models.job.read_keyval(path)
86 status_version = job_keyval.get("status_version", 0)
jadmanski6e8bf752008-05-14 00:17:48 +000087
jadmanski0afbb632008-06-06 21:10:57 +000088 # parse out the job
89 parser = status_lib.parser(status_version)
90 job = parser.make_job(path)
91 status_log = os.path.join(path, "status.log")
92 if not os.path.exists(status_log):
93 status_log = os.path.join(path, "status")
94 if not os.path.exists(status_log):
95 tko_utils.dprint("! Unable to parse job, no status file")
96 return
mbligh96cf0512008-04-17 15:25:38 +000097
jadmanski0afbb632008-06-06 21:10:57 +000098 # parse the status logs
99 tko_utils.dprint("+ Parsing dir=%s, jobname=%s" % (path, jobname))
100 status_lines = open(status_log).readlines()
101 parser.start(job)
102 tests = parser.end(status_lines)
103 job.tests = tests
mbligh96cf0512008-04-17 15:25:38 +0000104
jadmanski0afbb632008-06-06 21:10:57 +0000105 # check for failures
106 message_lines = [""]
107 for test in job.tests:
108 if not test.subdir:
109 continue
110 tko_utils.dprint("* testname, status, reason: %s %s %s"
111 % (test.subdir, test.status, test.reason))
112 if test.status in ("FAIL", "WARN"):
113 message_lines.append(format_failure_message(
114 jobname, test.kernel.base, test.subdir,
115 test.status, test.reason))
116 message = "\n".join(message_lines)
mbligh96cf0512008-04-17 15:25:38 +0000117
jadmanski0afbb632008-06-06 21:10:57 +0000118 # send out a email report of failure
119 if len(message) > 2 and mail_on_failure:
120 tko_utils.dprint("Sending email report of failure on %s to %s"
121 % (jobname, job.user))
122 mailfailure(jobname, job, message)
mbligh96cf0512008-04-17 15:25:38 +0000123
jadmanski0afbb632008-06-06 21:10:57 +0000124 # write the job into the database
125 db.insert_job(jobname, job)
126 db.commit()
mbligh26b992b2008-02-19 15:46:21 +0000127
128
jadmanski8e9c2572008-11-11 00:29:02 +0000129def _get_job_subdirs(path):
130 """
131 Returns a list of job subdirectories at path. Returns None if the test
132 is itself a job directory. Does not recurse into the subdirs.
133 """
134 # if there's a .machines file, use it to get the subdirs
jadmanski0afbb632008-06-06 21:10:57 +0000135 machine_list = os.path.join(path, ".machines")
136 if os.path.exists(machine_list):
jadmanski8e9c2572008-11-11 00:29:02 +0000137 return set(line.strip() for line in file(machine_list))
138
139 # if this dir contains ONLY subdirectories, return them
140 contents = set(os.listdir(path))
141 contents.discard(".parse.lock")
142 subdirs = set(sub for sub in contents if
143 os.path.isdir(os.path.join(path, sub)))
144 if len(contents) == len(subdirs) != 0:
145 return subdirs
146
147 # this is a job directory, or something else we don't understand
148 return None
149
150
151def parse_path(db, path, level, reparse, mail_on_failure):
152 job_subdirs = _get_job_subdirs(path)
153 if job_subdirs is not None:
jadmanski0afbb632008-06-06 21:10:57 +0000154 # multi-machine job
jadmanski8e9c2572008-11-11 00:29:02 +0000155 for subdir in job_subdirs:
156 jobpath = os.path.join(path, subdir)
157 parse_path(db, jobpath, level + 1, reparse, mail_on_failure)
jadmanski0afbb632008-06-06 21:10:57 +0000158 else:
159 # single machine job
160 job_elements = path.split("/")[-level:]
161 jobname = "/".join(job_elements)
162 try:
163 db.run_with_retry(parse_one, db, jobname, path,
164 reparse, mail_on_failure)
165 except Exception:
166 traceback.print_exc()
mblighbb7b8912006-10-08 03:59:02 +0000167
168
mbligh96cf0512008-04-17 15:25:38 +0000169def main():
jadmanski0afbb632008-06-06 21:10:57 +0000170 options, args = parse_args()
171 results_dir = os.path.abspath(args[0])
172 assert os.path.exists(results_dir)
mbligh96cf0512008-04-17 15:25:38 +0000173
jadmanskid5ab8c52008-12-03 16:27:07 +0000174 pid_file_manager = pidfile.PidFileManager("parser", results_dir)
mbligh96cf0512008-04-17 15:25:38 +0000175
jadmanskid5ab8c52008-12-03 16:27:07 +0000176 if options.write_pidfile:
177 pid_file_manager.open_file()
mbligh96cf0512008-04-17 15:25:38 +0000178
jadmanskid5ab8c52008-12-03 16:27:07 +0000179 try:
180 # build up the list of job dirs to parse
181 if options.singledir:
182 jobs_list = [results_dir]
183 else:
184 jobs_list = [os.path.join(results_dir, subdir)
185 for subdir in os.listdir(results_dir)]
186
187 # build up the database
188 db = tko_db.db(autocommit=False, host=options.db_host,
189 user=options.db_user, password=options.db_pass,
190 database=options.db_name)
191
192 # parse all the jobs
193 for path in jobs_list:
194 lockfile = open(os.path.join(path, ".parse.lock"), "w")
195 flags = fcntl.LOCK_EX
196 if options.noblock:
197 flags != fcntl.LOCK_NB
198 try:
199 fcntl.flock(lockfile, flags)
200 except IOError, e:
201 # was this because the lock is unavailable?
202 if e.errno == errno.EWOULDBLOCK:
203 lockfile.close()
204 continue
205 else:
206 raise # something unexpected happened
207 try:
208 parse_path(db, path, options.level, options.reparse,
209 options.mailit)
210 finally:
211 fcntl.flock(lockfile, fcntl.LOCK_UN)
jadmanski0afbb632008-06-06 21:10:57 +0000212 lockfile.close()
jadmanskid5ab8c52008-12-03 16:27:07 +0000213 except:
214 pid_file_manager.close_file(1)
215 raise
216 else:
217 pid_file_manager.close_file(0)
mbligh71d340d2008-03-05 15:51:16 +0000218
mbligh532cb272007-11-26 18:54:20 +0000219
mbligh96cf0512008-04-17 15:25:38 +0000220if __name__ == "__main__":
jadmanski0afbb632008-06-06 21:10:57 +0000221 main()