autoserv revision 9554eb4da688d7911b0a69cdb5c58cf77d6386d6
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, logging.config
10
11import common
12from autotest_lib.server import server_job, utils, autoserv_parser, autotest
13from autotest_lib.client.common_lib import pidfile
14
15def run_autoserv(pid_file_manager, results, parser):
16    # send stdin to /dev/null
17    dev_null = os.open(os.devnull, os.O_RDONLY)
18    os.dup2(dev_null, sys.stdin.fileno())
19    os.close(dev_null)
20
21    # Create separate process group
22    os.setpgrp()
23
24    # Implement SIGTERM handler
25    def handle_sigint(signum, frame):
26        if pid_file_manager:
27            pid_file_manager.close_file(1, signal.SIGTERM)
28        os.killpg(os.getpgrp(), signal.SIGKILL)
29
30    # Set signal handler
31    signal.signal(signal.SIGTERM, handle_sigint)
32
33    # Get a useful value for running 'USER'
34    realuser = os.environ.get('USER')
35    if not realuser:
36        realuser = 'anonymous'
37
38    if parser.options.machines:
39        machines = parser.options.machines.replace(',', ' ').strip().split()
40    else:
41        machines = []
42    machines_file = parser.options.machines_file
43    label = parser.options.label
44    user = parser.options.user
45    client = parser.options.client
46    server = parser.options.server
47    install_before = parser.options.install_before
48    install_after = parser.options.install_after
49    verify = parser.options.verify
50    repair = parser.options.repair
51    cleanup = parser.options.cleanup
52    no_tee = parser.options.no_tee
53    parse_job = parser.options.parse_job
54    host_protection = parser.options.host_protection
55    ssh_user = parser.options.ssh_user
56    ssh_port = parser.options.ssh_port
57    ssh_pass = parser.options.ssh_pass
58    collect_crashinfo = parser.options.collect_crashinfo
59
60    # can't be both a client and a server side test
61    if client and server:
62        print "Can not specify a test as both server and client!"
63        sys.exit(1)
64
65    if len(parser.args) < 1 and not (verify or repair or cleanup
66                                     or collect_crashinfo):
67        print parser.parser.print_help()
68        sys.exit(1)
69
70    # We have a control file unless it's just a verify/repair/cleanup job
71    if len(parser.args) > 0:
72        control = parser.args[0]
73    else:
74        control = None
75
76    if machines_file:
77        machines = []
78        for m in open(machines_file, 'r').readlines():
79            # remove comments, spaces
80            m = re.sub('#.*', '', m).strip()
81            if m:
82                machines.append(m)
83        print "Read list of machines from file: %s" % machines_file
84        print ','.join(machines)
85
86    if machines:
87        for machine in machines:
88            if not machine or re.search('\s', machine):
89                print "Invalid machine %s" % str(machine)
90                sys.exit(1)
91        machines = list(set(machines))
92        machines.sort()
93
94    job = server_job.server_job(control, parser.args[1:], results, label,
95                                user, machines, client, parse_job,
96                                ssh_user, ssh_port, ssh_pass)
97    if results:
98        debug_dir = os.path.join(results, 'debug')
99        stdout = os.path.join(debug_dir, 'autoserv.stdout')
100        stderr = os.path.join(debug_dir, 'autoserv.stderr')
101        if no_tee:
102            job.stdout.redirect(stdout)
103            job.stderr.redirect(stderr)
104        else:
105            job.stdout.tee_redirect(stdout)
106            job.stderr.tee_redirect(stderr)
107
108    # perform checks
109    job.precheck()
110
111    # run the job
112    exit_code = 0
113    try:
114        if repair:
115            job.repair(host_protection)
116        elif verify:
117            job.verify()
118        else:
119            try:
120                job.run(cleanup, install_before, install_after,
121                        only_collect_crashinfo=collect_crashinfo)
122            finally:
123                while job.hosts:
124                    host = job.hosts.pop()
125                    host.close()
126    except:
127        exit_code = 1
128        traceback.print_exc()
129
130    if pid_file_manager:
131        pid_file_manager.num_tests_failed = job.num_tests_failed
132        pid_file_manager.close_file(exit_code)
133    job.cleanup_parser()
134
135    sys.exit(exit_code)
136
137
138def main():
139    # grab the parser
140    parser = autoserv_parser.autoserv_parser
141    parser.parse_args()
142
143    if len(sys.argv) == 1:
144        parser.parser.print_help()
145        sys.exit(1)
146
147    results = parser.options.results
148    if not parser.options.no_logging:
149        if not results:
150            results = 'results.' + time.strftime('%Y-%m-%d-%H.%M.%S')
151        results  = os.path.abspath(results)
152        resultdir_exists = os.path.exists(os.path.join(results, 'control.srv'))
153        if not parser.options.collect_crashinfo and resultdir_exists:
154            error = "Error: results directory already exists: %s\n" % results
155            sys.stderr.write(error)
156            sys.exit(1)
157
158        # Now that we certified that there's no leftover results dir from
159        # previous jobs, lets create the result dir since the logging system
160        # needs to create the log file in there.
161        if not os.path.isdir(results):
162            os.makedirs(results)
163        os.environ['AUTOSERV_RESULTS'] = results
164        serverdir = os.path.dirname(__file__)
165        logging.config.fileConfig('%s/debug_server.ini' % serverdir)
166        logging.info("Results placed in %s" % results)
167    else:
168        # If we supply -N, no results dir will be generated, so
169        # we'll configure the logging system on code.
170        stamp = '[%(asctime)s - %(levelname)-8s] %(message)s'
171        root_logger = logging.getLogger()
172        formatter = logging.Formatter(stamp, datefmt='%H:%M:%S')
173        # Let's verify if we already have handlers for the root logger 
174        # at this point.
175        if len(root_logger.handlers) == 0:
176            autoserv_handler = logging.StreamHandler(sys.stdout,)
177            autoserv_handler.setFormatter(formatter)
178            root_logger.addHandler(autoserv_handler)
179        else:
180            # If we already have any handlers up at this point, let's
181            # just configure this one we already have.
182            root_logger.handlers[0].setFormatter(formatter)
183
184        # When the -N flag is being used, we are assuming DEBUG level for the
185        # execution. We could read the level from the configuration file, 
186        # but I am not sure if this is the right way to go, since we are doing
187        # all the configuration on code (lmr).
188        root_logger.setLevel(logging.DEBUG)
189
190
191    if parser.options.write_pidfile:
192        pid_file_manager = pidfile.PidFileManager("autoserv", results)
193        pid_file_manager.open_file()
194    else:
195        pid_file_manager = None
196
197    autotest.BaseAutotest.set_install_in_tmpdir(
198        parser.options.install_in_tmpdir)
199
200    exit_code = 0
201    try:
202        try:
203            run_autoserv(pid_file_manager, results, parser)
204        except SystemExit, e:
205            exit_code = e.code
206        except:
207            traceback.print_exc()
208            # If we don't know what happened, we'll classify it as
209            # an 'abort' and return 1.
210            exit_code = 1
211    finally:
212        if pid_file_manager:
213            pid_file_manager.close_file(exit_code)
214    sys.exit(exit_code)
215
216
217if __name__ == '__main__':
218    main()
219