blob: fc4dfc259e4f58a22668109e8bc48efcddec7e92 [file] [log] [blame]
Paul Pendleburyf807c182011-04-05 11:24:34 -07001# Copyright (c) 2011 The Chromium OS 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
Paul Pendlebury7c1fdcf2011-05-04 12:39:15 -07005"""Utility classes used by server_job.distribute_across_machines().
Paul Pendleburyf807c182011-04-05 11:24:34 -07006
Paul Pendlebury7c1fdcf2011-05-04 12:39:15 -07007test_item: extends the basic test tuple to add include/exclude attributes and
8 pre/post actions.
Paul Pendleburyf807c182011-04-05 11:24:34 -07009
10machine_worker: is a thread that manages running tests on a host. It
11 verifies test are valid for a host using the test attributes from test_item
12 and the host attributes from host_attributes.
13"""
14
15
16import logging, os, Queue, threading
17from autotest_lib.client.common_lib import error, utils
18from autotest_lib.server import autotest, hosts, host_attributes, subcommand
19
20
21class test_item(object):
22 """Adds machine verification logic to the basic test tuple.
23
24 Tests can either be tuples of the existing form ('testName', {args}) or the
Paul Pendlebury7c1fdcf2011-05-04 12:39:15 -070025 extended form ('testname', {args}, ['include'], ['exclude'], ['actions'])
26 where include and exclude are lists of attributes and actions is a list of
27 strings. A machine must have all the attributes in include and must not
28 have any of the attributes in exclude to be valid for the test. Actions
29 strings can include 'reboot_before' and 'reboot_after'.
Paul Pendleburyf807c182011-04-05 11:24:34 -070030 """
31
32 def __init__(self, test_name, test_args, include_attribs=None,
Paul Pendlebury7c1fdcf2011-05-04 12:39:15 -070033 exclude_attribs=None, pre_post_actions=None):
Paul Pendleburyf807c182011-04-05 11:24:34 -070034 """Creates an instance of test_item.
35
36 Args:
37 test_name: string, name of test to execute.
38 test_args: dictionary, arguments to pass into test.
39 include_attribs: attributes a machine must have to run test.
40 exclude_attribs: attributes preventing a machine from running test.
Paul Pendlebury7c1fdcf2011-05-04 12:39:15 -070041 pre_post_actions: reboot before/after running the test.
Paul Pendleburyf807c182011-04-05 11:24:34 -070042 """
43 self.test_name = test_name
44 self.test_args = test_args
45 self.inc_set = None
46 if include_attribs is not None:
47 self.inc_set = set(include_attribs)
48 self.exc_set = None
49 if exclude_attribs is not None:
50 self.exc_set = set(exclude_attribs)
Paul Pendlebury7c1fdcf2011-05-04 12:39:15 -070051 self.pre_post = []
52 if pre_post_actions is not None:
53 self.pre_post = pre_post_actions
Paul Pendleburyf807c182011-04-05 11:24:34 -070054
55 def __str__(self):
56 """Return an info string of this test."""
57 params = ['%s=%s' % (k, v) for k, v in self.test_args.items()]
58 msg = '%s(%s)' % (self.test_name, params)
59 if self.inc_set: msg += ' include=%s' % [s for s in self.inc_set]
60 if self.exc_set: msg += ' exclude=%s' % [s for s in self.exc_set]
Paul Pendlebury7c1fdcf2011-05-04 12:39:15 -070061 if self.pre_post: msg += ' actions=%s' % self.pre_post
Paul Pendleburyf807c182011-04-05 11:24:34 -070062 return msg
63
64 def validate(self, machine_attributes):
65 """Check if this test can run on machine with machine_attributes.
66
67 If the test has include attributes, a candidate machine must have all
68 the attributes to be valid.
69
70 If the test has exclude attributes, a candidate machine cannot have any
71 of the attributes to be valid.
72
73 Args:
74 machine_attributes: set, True attributes of candidate machine.
75
76 Returns:
77 True/False if the machine is valid for this test.
78 """
79 if self.inc_set is not None:
80 if not self.inc_set <= machine_attributes: return False
81 if self.exc_set is not None:
82 if self.exc_set & machine_attributes: return False
83 return True
84
Paul Pendlebury7c1fdcf2011-05-04 12:39:15 -070085 def run_test(self, client_at, work_dir='.'):
86 """Runs the test on the client using autotest.
87
88 Args:
89 client_at: Autotest instance for this host.
90 work_dir: Directory to use for results and log files.
91 """
92 if 'reboot_before' in self.pre_post:
93 client_at.host.reboot()
94
Paul Pendlebury92a90742011-05-25 11:54:34 -070095 try:
96 client_at.run_test(self.test_name, results_dir=work_dir,
97 **self.test_args)
98 finally:
99 if 'reboot_after' in self.pre_post:
100 client_at.host.reboot()
Paul Pendlebury7c1fdcf2011-05-04 12:39:15 -0700101
Paul Pendleburyf807c182011-04-05 11:24:34 -0700102
103class machine_worker(threading.Thread):
104 """Thread that runs tests on a remote host machine."""
105
Paul Pendleburye4afc772011-04-26 11:20:15 -0700106 def __init__(self, server_job, machine, work_dir, test_queue, queue_lock,
107 continuous_parsing=False):
Paul Pendleburyf807c182011-04-05 11:24:34 -0700108 """Creates an instance of machine_worker to run tests on a remote host.
109
110 Retrieves that host attributes for this machine and creates the set of
111 True attributes to validate against test include/exclude attributes.
112
113 Creates a directory to hold the log files for tests run and writes the
114 hostname and tko parser version into keyvals file.
115
116 Args:
Paul Pendlebury1f6f3e72011-04-13 11:16:44 -0700117 server_job: run tests for this server_job.
Paul Pendleburyf807c182011-04-05 11:24:34 -0700118 machine: name of remote host.
119 work_dir: directory server job is using.
120 test_queue: queue of tests.
121 queue_lock: lock protecting test_queue.
Paul Pendleburye4afc772011-04-26 11:20:15 -0700122 continuous_parsing: bool, enable continuous parsing.
Paul Pendleburyf807c182011-04-05 11:24:34 -0700123 """
124 threading.Thread.__init__(self)
Paul Pendlebury1f6f3e72011-04-13 11:16:44 -0700125 self._server_job = server_job
Paul Pendleburyf807c182011-04-05 11:24:34 -0700126 self._test_queue = test_queue
127 self._test_queue_lock = queue_lock
Paul Pendleburye4afc772011-04-26 11:20:15 -0700128 self._continuous_parsing = continuous_parsing
Paul Pendleburyf807c182011-04-05 11:24:34 -0700129 self._tests_run = 0
130 self._machine = machine
131 self._host = hosts.create_host(self._machine)
132 self._client_at = autotest.Autotest(self._host)
133 client_attributes = host_attributes.host_attributes(machine)
Paul Pendlebury7c1fdcf2011-05-04 12:39:15 -0700134 self.attribute_set = set(client_attributes.get_attributes())
Paul Pendlebury3dd080e2011-05-06 12:30:32 -0700135 self._results_dir = work_dir
136 # Only create machine subdir when running a multi-machine job.
137 if not self._machine in work_dir:
138 self._results_dir = os.path.join(work_dir, self._machine)
139 if not os.path.exists(self._results_dir):
140 os.makedirs(self._results_dir)
141 machine_data = {'hostname': self._machine,
142 'status_version': str(1)}
143 utils.write_keyval(self._results_dir, machine_data)
Paul Pendleburyf807c182011-04-05 11:24:34 -0700144
145 def __str__(self):
146 attributes = [a for a in self.attribute_set]
147 return '%s attributes=%s' % (self._machine, attributes)
148
149 def get_test(self):
150 """Return a test from the queue to run on this host.
151
152 The test queue can be non-empty, but still not contain a test that is
153 valid for this machine. This function will take exclusive access to
154 the queue via _test_queue_lock and repeatedly pop tests off the queue
155 until finding a valid test or depleting the queue. In either case if
156 invalid tests have been popped from the queue, they are pushed back
157 onto the queue before returning.
158
159 Returns:
160 test_item, or None if no more tests exist for this machine.
161 """
162 good_test = None
163 skipped_tests = []
164
165 with self._test_queue_lock:
166 while True:
167 try:
168 canidate_test = self._test_queue.get_nowait()
169 # Check if test is valid for this machine.
170 if canidate_test.validate(self.attribute_set):
171 good_test = canidate_test
172 break
173 skipped_tests.append(canidate_test)
174
175 except Queue.Empty:
176 break
177
178 # Return any skipped tests to the queue.
179 for st in skipped_tests:
180 self._test_queue.put(st)
181
182 return good_test
183
Paul Pendleburye4afc772011-04-26 11:20:15 -0700184 def run(self):
185 """Use subcommand to fork process and execute tests.
186
187 The forked processes prevents log files from simultaneous tests
188 interweaving with each other. Logging doesn't communicate host autotest
189 to client autotest, it communicates host module to client autotest. So
190 different server side autotest instances share the same module and
191 require split processes to have clean logging.
192 """
193 sub_cmd = subcommand.subcommand(self._run,
194 [],
Paul Pendlebury1f6f3e72011-04-13 11:16:44 -0700195 self._results_dir)
Paul Pendleburyf807c182011-04-05 11:24:34 -0700196 sub_cmd.fork_start()
197 sub_cmd.fork_waitfor()
198
Paul Pendleburye4afc772011-04-26 11:20:15 -0700199 def _run(self):
200 """Executes tests on the host machine.
Paul Pendlebury1f6f3e72011-04-13 11:16:44 -0700201
Paul Pendleburye4afc772011-04-26 11:20:15 -0700202 If continuous parsing was requested, start the parser before running
203 tests.
Paul Pendlebury1f6f3e72011-04-13 11:16:44 -0700204 """
Paul Pendleburye4afc772011-04-26 11:20:15 -0700205 if self._continuous_parsing:
206 self._server_job._parse_job += "/" + self._machine
207 self._server_job._using_parser = True
208 self._server_job.machines = [self._machine]
209 self._server_job.push_execution_context(self._machine)
210 self._server_job.init_parser()
Paul Pendleburyf807c182011-04-05 11:24:34 -0700211
Paul Pendleburyf807c182011-04-05 11:24:34 -0700212 while True:
213 active_test = self.get_test()
214 if active_test is None:
215 break
216
Paul Pendleburyf807c182011-04-05 11:24:34 -0700217 logging.info('%s running %s', self._machine, active_test)
218 try:
Paul Pendlebury7c1fdcf2011-05-04 12:39:15 -0700219 active_test.run_test(self._client_at, self._results_dir)
Paul Pendlebury92a90742011-05-25 11:54:34 -0700220 except error.AutoservError:
221 logging.exception('Autoserv error running "%s".', active_test)
222 except error.AutotestError:
223 logging.exception('Autotest error running "%s".', active_test)
Paul Pendleburyf807c182011-04-05 11:24:34 -0700224 except Exception:
225 logging.exception('Exception running test "%s".', active_test)
226 raise
227 finally:
228 self._test_queue.task_done()
229 self._tests_run += 1
230
Paul Pendleburye4afc772011-04-26 11:20:15 -0700231 if self._continuous_parsing:
232 self._server_job.cleanup_parser()
Paul Pendleburyf807c182011-04-05 11:24:34 -0700233 logging.info('%s completed %d tests.', self._machine, self._tests_run)