1 # Copyright 2017 The Chromium 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 5 """Run a job against Autotest. 6 7 See http://goto.google.com/monitor_db_per_job_refactor 8 9 See also lucifer_run_job in 10 https://chromium.googlesource.com/chromiumos/infra/lucifer 11 12 job_reporter is a thin wrapper around lucifer_run_job and only updates the 13 Autotest database according to status events. 14 """ 15 16 from __future__ import absolute_import 17 from __future__ import division 18 from __future__ import print_function 19 20 import atexit 21 import argparse 22 import logging 23 import os 24 import sys 25 26 from lucifer import autotest 27 from lucifer import eventlib 28 from lucifer import handlers 29 from lucifer import jobx 30 from lucifer import leasing 31 from lucifer import loglib 32 33 logger = logging.getLogger(__name__) 34 35 36 def main(args): 37 """Main function 38 39 @param args: list of command line args 40 """ 41 args = _parse_args_and_configure_logging(args) 42 logger.info('Starting with args: %r', args) 43 with leasing.obtain_lease(_lease_path(args.jobdir, args.job_id)): 44 autotest.monkeypatch() 45 ret = _main(args) 46 logger.info('Exiting normally with: %r', ret) 47 return ret 48 49 50 def _parse_args_and_configure_logging(args): 51 parser = argparse.ArgumentParser(prog='job_reporter', description=__doc__) 52 loglib.add_logging_options(parser) 53 54 # General configuration 55 parser.add_argument('--jobdir', default='/usr/local/autotest/leases', 56 help='Path to job leases directory.') 57 parser.add_argument('--run-job-path', default='/usr/bin/lucifer_run_job', 58 help='Path to lucifer_run_job binary') 59 parser.add_argument('--watcher-path', default='/usr/bin/lucifer_watcher', 60 help='Path to lucifer_watcher binary') 61 62 # Job specific 63 parser.add_argument('--job-id', type=int, required=True, 64 help='Autotest Job ID') 65 parser.add_argument('--lucifer-level', required=True, 66 help='Lucifer level') 67 parser.add_argument('--autoserv-exit', type=int, default=None, help=''' 68 autoserv exit status. If this is passed, then autoserv will not be run 69 as the caller has presumably already run it. 70 ''') 71 parser.add_argument('--need-gather', action='store_true', 72 help='Whether to gather logs' 73 ' (only with --lucifer-level GATHERING)') 74 parser.add_argument('--num-tests-failed', type=int, default=-1, 75 help='Number of tests failed' 76 ' (only with --need-gather)') 77 parser.add_argument('--results-dir', required=True, 78 help='Path to job leases directory.') 79 args = parser.parse_args(args) 80 loglib.configure_logging_with_args(parser, args) 81 return args 82 83 84 def _main(args): 85 """Main program body, running under a lease file. 86 87 @param args: Namespace object containing parsed arguments 88 """ 89 ts_mon_config = autotest.chromite_load('ts_mon_config') 90 metrics = autotest.chromite_load('metrics') 91 with ts_mon_config.SetupTsMonGlobalState( 92 'job_reporter', short_lived=True): 93 atexit.register(metrics.Flush) 94 return _run_autotest_job(args) 95 96 97 def _run_autotest_job(args): 98 """Run a job as seen from Autotest. 99 100 This include some Autotest setup and cleanup around lucifer starting 101 proper. 102 """ 103 handler = _make_handler(args) 104 ret = _run_lucifer_job(handler, args) 105 if handler.completed: 106 _mark_handoff_completed(args.job_id) 107 return ret 108 109 110 def _make_handler(args): 111 """Make event handler for lucifer_run_job.""" 112 models = autotest.load('frontend.afe.models') 113 if args.autoserv_exit is None: 114 # TODO(crbug.com/748234): autoserv not implemented yet. 115 raise NotImplementedError('not implemented yet (crbug.com/748234)') 116 job = models.Job.objects.get(id=args.job_id) 117 return handlers.EventHandler( 118 metrics=handlers.Metrics(), 119 job=job, 120 autoserv_exit=args.autoserv_exit, 121 ) 122 123 124 def _run_lucifer_job(event_handler, args): 125 """Run lucifer_run_job. 126 127 Issued events will be handled by event_handler. 128 129 @param event_handler: callable that takes an Event 130 @param args: parsed arguments 131 @returns: exit status of lucifer_run_job 132 """ 133 models = autotest.load('frontend.afe.models') 134 command_args = [args.run_job_path] 135 job = models.Job.objects.get(id=args.job_id) 136 command_args.extend([ 137 '-autotestdir', autotest.AUTOTEST_DIR, 138 '-watcherpath', args.watcher_path, 139 140 '-abortsock', _abort_sock_path(args.jobdir, args.job_id), 141 '-hosts', ','.join(jobx.hostnames(job)), 142 143 '-x-level', args.lucifer_level, 144 '-x-resultsdir', args.results_dir, 145 '-x-autoserv-exit', str(args.autoserv_exit), 146 ]) 147 if args.need_gather: 148 command_args.extend([ 149 '-x-need-gather', 150 '-x-num-tests-failed', str(args.num_tests_failed), 151 ]) 152 return eventlib.run_event_command( 153 event_handler=event_handler, args=command_args) 154 155 156 def _mark_handoff_completed(job_id): 157 models = autotest.load('frontend.afe.models') 158 handoff = models.JobHandoff.objects.get(job_id=job_id) 159 handoff.completed = True 160 handoff.save() 161 162 163 def _abort_sock_path(jobdir, job_id): 164 return _lease_path(jobdir, job_id) + '.sock' 165 166 167 def _lease_path(jobdir, job_id): 168 return os.path.join(jobdir, str(job_id)) 169 170 171 if __name__ == '__main__': 172 sys.exit(main(sys.argv[1:])) 173