autoserv.py revision 4608b005f15444d2ec4601b8274828ad52b5ea51
1#!/usr/bin/python -u
2# Copyright 2007-2008 Martin J. Bligh <mbligh@google.com>, Google Inc.
3# Released under the GPL v2
4
5"""
6Run an control file through the server side engine
7"""
8
9import sys, os, re, traceback, signal, time, logging, getpass
10
11import common
12
13from autotest_lib.client.common_lib.global_config import global_config
14require_atfork = global_config.get_config_value(
15        'AUTOSERV', 'require_atfork_module', type=bool, default=True)
16
17try:
18    import atfork
19    atfork.monkeypatch_os_fork_functions()
20    import atfork.stdlib_fixer
21    # Fix the Python standard library for threading+fork safety with its
22    # internal locks.  http://code.google.com/p/python-atfork/
23    import warnings
24    warnings.filterwarnings('ignore', 'logging module already imported')
25    atfork.stdlib_fixer.fix_logging_module()
26except ImportError, e:
27    from autotest_lib.client.common_lib import global_config
28    if global_config.global_config.get_config_value(
29            'AUTOSERV', 'require_atfork_module', type=bool, default=False):
30        print >>sys.stderr, 'Please run utils/build_externals.py'
31        print e
32        sys.exit(1)
33
34from autotest_lib.server import server_logging_config
35from autotest_lib.server import server_job, utils, autoserv_parser, autotest
36from autotest_lib.client.common_lib import pidfile, logging_manager
37
38def run_autoserv(pid_file_manager, results, parser):
39    # send stdin to /dev/null
40    dev_null = os.open(os.devnull, os.O_RDONLY)
41    os.dup2(dev_null, sys.stdin.fileno())
42    os.close(dev_null)
43
44    # Create separate process group
45    os.setpgrp()
46
47    # Implement SIGTERM handler
48    def handle_sigterm(signum, frame):
49        if pid_file_manager:
50            pid_file_manager.close_file(1, signal.SIGTERM)
51        os.killpg(os.getpgrp(), signal.SIGKILL)
52
53    # Set signal handler
54    signal.signal(signal.SIGTERM, handle_sigterm)
55
56    # Server side tests that call shell scripts often depend on $USER being set
57    # but depending on how you launch your autotest scheduler it may not be set.
58    os.environ['USER'] = getpass.getuser()
59
60    if parser.options.machines:
61        machines = parser.options.machines.replace(',', ' ').strip().split()
62    else:
63        machines = []
64    machines_file = parser.options.machines_file
65    label = parser.options.label
66    group_name = parser.options.group_name
67    user = parser.options.user
68    client = parser.options.client
69    server = parser.options.server
70    install_before = parser.options.install_before
71    install_after = parser.options.install_after
72    verify = parser.options.verify
73    repair = parser.options.repair
74    cleanup = parser.options.cleanup
75    no_tee = parser.options.no_tee
76    parse_job = parser.options.parse_job
77    execution_tag = parser.options.execution_tag
78    if not execution_tag:
79        execution_tag = parse_job
80    host_protection = parser.options.host_protection
81    ssh_user = parser.options.ssh_user
82    ssh_port = parser.options.ssh_port
83    ssh_pass = parser.options.ssh_pass
84    collect_crashinfo = parser.options.collect_crashinfo
85
86    # can't be both a client and a server side test
87    if client and server:
88        print "Can not specify a test as both server and client!"
89        sys.exit(1)
90
91    if len(parser.args) < 1 and not (verify or repair or cleanup
92                                     or collect_crashinfo):
93        print parser.parser.print_help()
94        sys.exit(1)
95
96    # We have a control file unless it's just a verify/repair/cleanup job
97    if len(parser.args) > 0:
98        control = parser.args[0]
99    else:
100        control = None
101
102    if machines_file:
103        machines = []
104        for m in open(machines_file, 'r').readlines():
105            # remove comments, spaces
106            m = re.sub('#.*', '', m).strip()
107            if m:
108                machines.append(m)
109        print "Read list of machines from file: %s" % machines_file
110        print ','.join(machines)
111
112    if machines:
113        for machine in machines:
114            if not machine or re.search('\s', machine):
115                print "Invalid machine %s" % str(machine)
116                sys.exit(1)
117        machines = list(set(machines))
118        machines.sort()
119
120    if group_name and len(machines) < 2:
121        print ("-G %r may only be supplied with more than one machine."
122               % group_name)
123        sys.exit(1)
124
125    job = server_job.server_job(control, parser.args[1:], results, label,
126                                user, machines, client, parse_job,
127                                ssh_user, ssh_port, ssh_pass,
128                                group_name=group_name, tag=execution_tag)
129    job.logging.start_logging()
130    job.init_parser()
131
132    # perform checks
133    job.precheck()
134
135    # run the job
136    exit_code = 0
137    try:
138        try:
139            if repair:
140                job.repair(host_protection)
141            elif verify:
142                job.verify()
143            else:
144                job.run(cleanup, install_before, install_after,
145                        only_collect_crashinfo=collect_crashinfo)
146        finally:
147            while job.hosts:
148                host = job.hosts.pop()
149                host.close()
150    except:
151        exit_code = 1
152        traceback.print_exc()
153
154    if pid_file_manager:
155        pid_file_manager.num_tests_failed = job.num_tests_failed
156        pid_file_manager.close_file(exit_code)
157    job.cleanup_parser()
158
159    sys.exit(exit_code)
160
161
162def main():
163    # grab the parser
164    parser = autoserv_parser.autoserv_parser
165    parser.parse_args()
166
167    if len(sys.argv) == 1:
168        parser.parser.print_help()
169        sys.exit(1)
170
171    if parser.options.no_logging:
172        results = None
173    else:
174        results = parser.options.results
175        if not results:
176            results = 'results.' + time.strftime('%Y-%m-%d-%H.%M.%S')
177        results  = os.path.abspath(results)
178        resultdir_exists = os.path.exists(os.path.join(results, 'control.srv'))
179        if not parser.options.use_existing_results and resultdir_exists:
180            error = "Error: results directory already exists: %s\n" % results
181            sys.stderr.write(error)
182            sys.exit(1)
183
184        # Now that we certified that there's no leftover results dir from
185        # previous jobs, lets create the result dir since the logging system
186        # needs to create the log file in there.
187        if not os.path.isdir(results):
188            os.makedirs(results)
189
190    logging_manager.configure_logging(
191            server_logging_config.ServerLoggingConfig(), results_dir=results,
192            use_console=not parser.options.no_tee,
193            verbose=parser.options.verbose,
194            no_console_prefix=parser.options.no_console_prefix)
195    if results:
196        logging.info("Results placed in %s" % results)
197
198        # wait until now to perform this check, so it get properly logged
199        if parser.options.use_existing_results and not resultdir_exists:
200            logging.error("No existing results directory found: %s", results)
201            sys.exit(1)
202
203
204    if parser.options.write_pidfile:
205        pid_file_manager = pidfile.PidFileManager(parser.options.pidfile_label,
206                                                  results)
207        pid_file_manager.open_file()
208    else:
209        pid_file_manager = None
210
211    autotest.BaseAutotest.set_install_in_tmpdir(
212        parser.options.install_in_tmpdir)
213
214    exit_code = 0
215    try:
216        try:
217            run_autoserv(pid_file_manager, results, parser)
218        except SystemExit, e:
219            exit_code = e.code
220        except:
221            traceback.print_exc()
222            # If we don't know what happened, we'll classify it as
223            # an 'abort' and return 1.
224            exit_code = 1
225    finally:
226        if pid_file_manager:
227            pid_file_manager.close_file(exit_code)
228    sys.exit(exit_code)
229
230
231if __name__ == '__main__':
232    main()
233