blob: 970f88704475129cec6e8cd52d1ee4742e6226b0 [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
95 client_at.run_test(self.test_name,
96 results_dir=work_dir,
97 **self.test_args)
98
99 if 'reboot_after' in self.pre_post:
100 client_at.host.reboot()
101
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 Pendleburyf807c182011-04-05 11:24:34 -0700135 self._results_dir = os.path.join(work_dir, self._machine)
136 if not os.path.exists(self._results_dir):
137 os.makedirs(self._results_dir)
138 machine_data = {'hostname': self._machine,
139 'status_version': str(1)}
140 utils.write_keyval(self._results_dir, machine_data)
141
142 def __str__(self):
143 attributes = [a for a in self.attribute_set]
144 return '%s attributes=%s' % (self._machine, attributes)
145
146 def get_test(self):
147 """Return a test from the queue to run on this host.
148
149 The test queue can be non-empty, but still not contain a test that is
150 valid for this machine. This function will take exclusive access to
151 the queue via _test_queue_lock and repeatedly pop tests off the queue
152 until finding a valid test or depleting the queue. In either case if
153 invalid tests have been popped from the queue, they are pushed back
154 onto the queue before returning.
155
156 Returns:
157 test_item, or None if no more tests exist for this machine.
158 """
159 good_test = None
160 skipped_tests = []
161
162 with self._test_queue_lock:
163 while True:
164 try:
165 canidate_test = self._test_queue.get_nowait()
166 # Check if test is valid for this machine.
167 if canidate_test.validate(self.attribute_set):
168 good_test = canidate_test
169 break
170 skipped_tests.append(canidate_test)
171
172 except Queue.Empty:
173 break
174
175 # Return any skipped tests to the queue.
176 for st in skipped_tests:
177 self._test_queue.put(st)
178
179 return good_test
180
Paul Pendleburye4afc772011-04-26 11:20:15 -0700181 def run(self):
182 """Use subcommand to fork process and execute tests.
183
184 The forked processes prevents log files from simultaneous tests
185 interweaving with each other. Logging doesn't communicate host autotest
186 to client autotest, it communicates host module to client autotest. So
187 different server side autotest instances share the same module and
188 require split processes to have clean logging.
189 """
190 sub_cmd = subcommand.subcommand(self._run,
191 [],
Paul Pendlebury1f6f3e72011-04-13 11:16:44 -0700192 self._results_dir)
Paul Pendleburyf807c182011-04-05 11:24:34 -0700193 sub_cmd.fork_start()
194 sub_cmd.fork_waitfor()
195
Paul Pendleburye4afc772011-04-26 11:20:15 -0700196 def _run(self):
197 """Executes tests on the host machine.
Paul Pendlebury1f6f3e72011-04-13 11:16:44 -0700198
Paul Pendleburye4afc772011-04-26 11:20:15 -0700199 If continuous parsing was requested, start the parser before running
200 tests.
Paul Pendlebury1f6f3e72011-04-13 11:16:44 -0700201 """
Paul Pendleburye4afc772011-04-26 11:20:15 -0700202 if self._continuous_parsing:
203 self._server_job._parse_job += "/" + self._machine
204 self._server_job._using_parser = True
205 self._server_job.machines = [self._machine]
206 self._server_job.push_execution_context(self._machine)
207 self._server_job.init_parser()
Paul Pendleburyf807c182011-04-05 11:24:34 -0700208
Paul Pendleburyf807c182011-04-05 11:24:34 -0700209 while True:
210 active_test = self.get_test()
211 if active_test is None:
212 break
213
Paul Pendleburyf807c182011-04-05 11:24:34 -0700214 logging.info('%s running %s', self._machine, active_test)
215 try:
Paul Pendlebury7c1fdcf2011-05-04 12:39:15 -0700216 active_test.run_test(self._client_at, self._results_dir)
Paul Pendleburyf807c182011-04-05 11:24:34 -0700217 except (error.AutoservError, error.AutotestError):
218 logging.exception('Error running test "%s".', active_test)
219 except Exception:
220 logging.exception('Exception running test "%s".', active_test)
221 raise
222 finally:
223 self._test_queue.task_done()
224 self._tests_run += 1
225
Paul Pendleburye4afc772011-04-26 11:20:15 -0700226 if self._continuous_parsing:
227 self._server_job.cleanup_parser()
Paul Pendleburyf807c182011-04-05 11:24:34 -0700228 logging.info('%s completed %d tests.', self._machine, self._tests_run)