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 7See http://goto.google.com/monitor_db_per_job_refactor 8 9See also https://chromium.googlesource.com/chromiumos/infra/lucifer 10 11job_reporter is a thin wrapper around lucifer and only updates the 12Autotest database according to status events. 13""" 14 15from __future__ import absolute_import 16from __future__ import division 17from __future__ import print_function 18 19import atexit 20import argparse 21import logging 22import os 23import sys 24 25from lucifer import autotest 26from lucifer import eventlib 27from lucifer import handlers 28from lucifer import jobx 29from lucifer import leasing 30from lucifer import loglib 31 32logger = logging.getLogger(__name__) 33 34 35def main(argv): 36 """Main function 37 38 @param argv: command line args 39 """ 40 print('job_reporter: Running with argv: %r' % argv, file=sys.stderr) 41 args = _parse_args_and_configure_logging(argv[1:]) 42 logger.info('Running with parsed 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 50def _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('--lucifer-path', default='/usr/bin/lucifer', 58 help='Path to lucifer binary') 59 60 # Job specific 61 62 # General 63 parser.add_argument('--lucifer-level', required=True, 64 help='Lucifer level', choices=['STARTING']) 65 parser.add_argument('--job-id', type=int, required=True, 66 help='Autotest Job ID') 67 parser.add_argument('--results-dir', required=True, 68 help='Path to job results directory.') 69 70 # STARTING flags 71 parser.add_argument('--execution-tag', default=None, 72 help='Autotest execution tag.') 73 parser.add_argument('--parsing-only', action='store_true', 74 help='Whether to only do parsing' 75 ' (only with --lucifer-level STARTING)') 76 77 args = parser.parse_args(args) 78 loglib.configure_logging_with_args(parser, args) 79 return args 80 81 82def _main(args): 83 """Main program body, running under a lease file. 84 85 @param args: Namespace object containing parsed arguments 86 """ 87 ts_mon_config = autotest.chromite_load('ts_mon_config') 88 metrics = autotest.chromite_load('metrics') 89 with ts_mon_config.SetupTsMonGlobalState( 90 'job_reporter', short_lived=True): 91 atexit.register(metrics.Flush) 92 return _run_autotest_job(args) 93 94 95def _run_autotest_job(args): 96 """Run a job as seen from Autotest. 97 98 This include some Autotest setup and cleanup around lucifer starting 99 proper. 100 """ 101 models = autotest.load('frontend.afe.models') 102 job = models.Job.objects.get(id=args.job_id) 103 _prepare_autotest_job_files(args, job) 104 handler = _make_handler(args, job) 105 ret = _run_lucifer_job(handler, args, job) 106 if handler.completed: 107 _mark_handoff_completed(args.job_id) 108 return ret 109 110 111def _prepare_autotest_job_files(args, job): 112 jobx.prepare_control_file(job, args.results_dir) 113 jobx.prepare_keyvals_files(job, args.results_dir) 114 115 116def _make_handler(args, job): 117 """Make event handler for lucifer.""" 118 return handlers.EventHandler( 119 metrics=handlers.Metrics(), 120 job=job, 121 autoserv_exit=None, 122 results_dir=args.results_dir, 123 ) 124 125 126def _run_lucifer_job(event_handler, args, job): 127 """Run lucifer test. 128 129 Issued events will be handled by event_handler. 130 131 @param event_handler: callable that takes an Event 132 @param args: parsed arguments 133 @returns: exit status of lucifer 134 """ 135 command_args = [args.lucifer_path] 136 command_args.extend([ 137 'test', 138 '-autotestdir', '/usr/local/autotest', 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 '-resultsdir', args.results_dir, 145 ]) 146 _add_level_specific_args(command_args, args, job) 147 return eventlib.run_event_command( 148 event_handler=event_handler, args=command_args) 149 150 151def _add_level_specific_args(command_args, args, job): 152 """Add level specific arguments for lucifer test. 153 154 command_args is modified in place. 155 """ 156 if args.lucifer_level == 'STARTING': 157 _add_starting_args(command_args, args, job) 158 else: 159 raise Exception('Invalid lucifer level %s' % args.lucifer_level) 160 161 162def _add_starting_args(command_args, args, job): 163 """Add STARTING level arguments for lucifer test. 164 165 command_args is modified in place. 166 """ 167 RebootAfter = autotest.load('frontend.afe.model_attributes').RebootAfter 168 command_args.extend([ 169 '-x-control-file', jobx.control_file_path(args.results_dir), 170 ]) 171 if args.execution_tag is not None: 172 command_args.extend(['-x-execution-tag', args.execution_tag]) 173 command_args.extend(['-x-job-owner', job.owner]) 174 command_args.extend(['-x-job-name', job.name]) 175 command_args.extend( 176 ['-x-reboot-after', 177 RebootAfter.get_string(job.reboot_after).lower()]) 178 if args.parsing_only: 179 command_args.append('-x-parse-only') 180 if job.run_reset: 181 command_args.append('-x-run-reset') 182 if jobx.is_client_job(job): 183 command_args.append('-x-client-test') 184 if jobx.needs_ssp(job): 185 command_args.append('-x-require-ssp') 186 test_source_build = job.keyval_dict().get('test_source_build', None) 187 if test_source_build: 188 command_args.extend(['-x-test-source-build', test_source_build]) 189 if job.parent_job_id: 190 command_args.extend(['-x-parent-job-id', str(job.parent_job_id)]) 191 192 193def _mark_handoff_completed(job_id): 194 models = autotest.load('frontend.afe.models') 195 handoff = models.JobHandoff.objects.get(job_id=job_id) 196 handoff.completed = True 197 handoff.save() 198 199 200def _abort_sock_path(jobdir, job_id): 201 return _lease_path(jobdir, job_id) + '.sock' 202 203 204def _lease_path(jobdir, job_id): 205 return os.path.join(jobdir, str(job_id)) 206 207 208if __name__ == '__main__': 209 sys.exit(main(sys.argv)) 210