• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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