blob: 0384f4b44836cabdd17bde86c197f8c219d4fbb0 [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 Pendlebury57593562011-06-15 10:45:49 -07005"""Utility classes used by site_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
Paul Pendlebury57593562011-06-15 10:45:49 -070016import logging, os, Queue
Paul Pendleburyf807c182011-04-05 11:24:34 -070017from autotest_lib.client.common_lib import error, utils
Paul Pendlebury57593562011-06-15 10:45:49 -070018from autotest_lib.server import autotest, hosts, host_attributes
Paul Pendleburyf807c182011-04-05 11:24:34 -070019
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 Pendlebury57593562011-06-15 10:45:49 -070025 extended form ('testname', {args}, {'include': [], 'exclude': [],
26 'attributes': []}) where include and exclude are lists of host attribute
27 labels and attributes is a list of strings. A machine must have all the
28 labels in include and must not have any of the labels in exclude to be valid
29 for the test. Attributes strings can include reboot_before, reboot_after,
30 and server_job.
Paul Pendleburyf807c182011-04-05 11:24:34 -070031 """
32
Dale Curtise3c43492011-07-13 10:54:45 -070033 def __init__(self, test_name, test_args, test_attribs=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.
Paul Pendlebury57593562011-06-15 10:45:49 -070039 test_attribs: Dictionary of test attributes. Valid keys are:
40 include - labels a machine must have to run a test.
41 exclude - labels preventing a machine from running a test.
42 attributes - reboot before/after test, run test as server job.
Paul Pendleburyf807c182011-04-05 11:24:34 -070043 """
44 self.test_name = test_name
45 self.test_args = test_args
Nirnimesh717b0922011-11-09 12:03:48 -080046 self.tagged_test_name = test_name
47 if test_args.get('tag'):
48 self.tagged_test_name = test_name + '.' + test_args.get('tag')
Paul Pendleburycfc0d6a2011-06-10 08:31:02 -070049
Dale Curtise3c43492011-07-13 10:54:45 -070050 if test_attribs is None:
51 test_attribs = {}
Paul Pendlebury57593562011-06-15 10:45:49 -070052 self.inc_set = set(test_attribs.get('include', []))
53 self.exc_set = set(test_attribs.get('exclude', []))
54 self.attributes = test_attribs.get('attributes', [])
Paul Pendleburyf807c182011-04-05 11:24:34 -070055
56 def __str__(self):
57 """Return an info string of this test."""
58 params = ['%s=%s' % (k, v) for k, v in self.test_args.items()]
59 msg = '%s(%s)' % (self.test_name, params)
Paul Pendleburycfc0d6a2011-06-10 08:31:02 -070060 if self.inc_set:
61 msg += ' include=%s' % [s for s in self.inc_set]
62 if self.exc_set:
63 msg += ' exclude=%s' % [s for s in self.exc_set]
Paul Pendlebury57593562011-06-15 10:45:49 -070064 if self.attributes:
65 msg += ' attributes=%s' % self.attributes
Paul Pendleburyf807c182011-04-05 11:24:34 -070066 return msg
67
68 def validate(self, machine_attributes):
69 """Check if this test can run on machine with machine_attributes.
70
71 If the test has include attributes, a candidate machine must have all
72 the attributes to be valid.
73
74 If the test has exclude attributes, a candidate machine cannot have any
75 of the attributes to be valid.
76
77 Args:
78 machine_attributes: set, True attributes of candidate machine.
79
80 Returns:
81 True/False if the machine is valid for this test.
82 """
83 if self.inc_set is not None:
Paul Pendleburycfc0d6a2011-06-10 08:31:02 -070084 if not self.inc_set <= machine_attributes:
85 return False
Paul Pendleburyf807c182011-04-05 11:24:34 -070086 if self.exc_set is not None:
Paul Pendleburycfc0d6a2011-06-10 08:31:02 -070087 if self.exc_set & machine_attributes:
88 return False
Paul Pendleburyf807c182011-04-05 11:24:34 -070089 return True
90
Paul Pendlebury7739b432011-06-17 09:31:57 -070091 def run_test(self, client_at, work_dir='.', server_job=None):
Paul Pendlebury7c1fdcf2011-05-04 12:39:15 -070092 """Runs the test on the client using autotest.
93
94 Args:
95 client_at: Autotest instance for this host.
96 work_dir: Directory to use for results and log files.
Paul Pendlebury57593562011-06-15 10:45:49 -070097 server_job: Server_Job instance to use to runs server tests.
Paul Pendlebury7c1fdcf2011-05-04 12:39:15 -070098 """
Paul Pendlebury57593562011-06-15 10:45:49 -070099 if 'reboot_before' in self.attributes:
Paul Pendlebury7c1fdcf2011-05-04 12:39:15 -0700100 client_at.host.reboot()
101
Paul Pendlebury92a90742011-05-25 11:54:34 -0700102 try:
Paul Pendlebury57593562011-06-15 10:45:49 -0700103 if 'server_job' in self.attributes:
Paul Pendlebury7739b432011-06-17 09:31:57 -0700104 if 'host' in self.test_args:
105 self.test_args['host'] = client_at.host
106 if server_job is not None:
107 logging.info('Running Server_Job=%s', self.test_name)
108 server_job.run_test(self.test_name, **self.test_args)
109 else:
110 logging.error('No Server_Job instance provided for test '
111 '%s.', self.test_name)
112 else:
113 client_at.run_test(self.test_name, results_dir=work_dir,
114 **self.test_args)
Paul Pendlebury92a90742011-05-25 11:54:34 -0700115 finally:
Paul Pendlebury57593562011-06-15 10:45:49 -0700116 if 'reboot_after' in self.attributes:
Paul Pendlebury92a90742011-05-25 11:54:34 -0700117 client_at.host.reboot()
Paul Pendlebury7c1fdcf2011-05-04 12:39:15 -0700118
Paul Pendleburyf807c182011-04-05 11:24:34 -0700119
Paul Pendlebury57593562011-06-15 10:45:49 -0700120class machine_worker(object):
121 """Worker that runs tests on a remote host machine."""
Paul Pendleburyf807c182011-04-05 11:24:34 -0700122
Paul Pendleburye4afc772011-04-26 11:20:15 -0700123 def __init__(self, server_job, machine, work_dir, test_queue, queue_lock,
124 continuous_parsing=False):
Paul Pendleburyf807c182011-04-05 11:24:34 -0700125 """Creates an instance of machine_worker to run tests on a remote host.
126
127 Retrieves that host attributes for this machine and creates the set of
128 True attributes to validate against test include/exclude attributes.
129
130 Creates a directory to hold the log files for tests run and writes the
131 hostname and tko parser version into keyvals file.
132
133 Args:
Paul Pendlebury1f6f3e72011-04-13 11:16:44 -0700134 server_job: run tests for this server_job.
Paul Pendleburyf807c182011-04-05 11:24:34 -0700135 machine: name of remote host.
136 work_dir: directory server job is using.
137 test_queue: queue of tests.
138 queue_lock: lock protecting test_queue.
Paul Pendleburye4afc772011-04-26 11:20:15 -0700139 continuous_parsing: bool, enable continuous parsing.
Paul Pendleburyf807c182011-04-05 11:24:34 -0700140 """
Paul Pendlebury1f6f3e72011-04-13 11:16:44 -0700141 self._server_job = server_job
Paul Pendleburyf807c182011-04-05 11:24:34 -0700142 self._test_queue = test_queue
143 self._test_queue_lock = queue_lock
Paul Pendleburye4afc772011-04-26 11:20:15 -0700144 self._continuous_parsing = continuous_parsing
Paul Pendleburyf807c182011-04-05 11:24:34 -0700145 self._tests_run = 0
146 self._machine = machine
147 self._host = hosts.create_host(self._machine)
148 self._client_at = autotest.Autotest(self._host)
149 client_attributes = host_attributes.host_attributes(machine)
Paul Pendlebury7c1fdcf2011-05-04 12:39:15 -0700150 self.attribute_set = set(client_attributes.get_attributes())
Paul Pendlebury3dd080e2011-05-06 12:30:32 -0700151 self._results_dir = work_dir
Paul Pendlebury57593562011-06-15 10:45:49 -0700152 if not os.path.exists(self._results_dir):
153 os.makedirs(self._results_dir)
154 machine_data = {'hostname': self._machine,
155 'status_version': str(1)}
156 utils.write_keyval(self._results_dir, machine_data)
Paul Pendleburyf807c182011-04-05 11:24:34 -0700157
158 def __str__(self):
159 attributes = [a for a in self.attribute_set]
160 return '%s attributes=%s' % (self._machine, attributes)
161
162 def get_test(self):
163 """Return a test from the queue to run on this host.
164
165 The test queue can be non-empty, but still not contain a test that is
166 valid for this machine. This function will take exclusive access to
167 the queue via _test_queue_lock and repeatedly pop tests off the queue
168 until finding a valid test or depleting the queue. In either case if
169 invalid tests have been popped from the queue, they are pushed back
170 onto the queue before returning.
171
172 Returns:
173 test_item, or None if no more tests exist for this machine.
174 """
175 good_test = None
176 skipped_tests = []
177
178 with self._test_queue_lock:
179 while True:
180 try:
181 canidate_test = self._test_queue.get_nowait()
182 # Check if test is valid for this machine.
183 if canidate_test.validate(self.attribute_set):
184 good_test = canidate_test
185 break
186 skipped_tests.append(canidate_test)
187
188 except Queue.Empty:
189 break
190
191 # Return any skipped tests to the queue.
192 for st in skipped_tests:
193 self._test_queue.put(st)
194
195 return good_test
196
Paul Pendleburye4afc772011-04-26 11:20:15 -0700197 def run(self):
Paul Pendleburye4afc772011-04-26 11:20:15 -0700198 """Executes tests on the host machine.
Paul Pendlebury1f6f3e72011-04-13 11:16:44 -0700199
Paul Pendleburye4afc772011-04-26 11:20:15 -0700200 If continuous parsing was requested, start the parser before running
201 tests.
Paul Pendlebury1f6f3e72011-04-13 11:16:44 -0700202 """
Dale Curtis6eaaf232011-08-10 12:43:43 -0700203 # Modify job.resultdir so that it points to the results directory for
Dale Curtis1ddf9e52011-08-05 13:10:58 -0700204 # the machine we're working on. Required so that server jobs will write
205 # to the proper location.
206 self._server_job.machines = [self._machine]
207 self._server_job.push_execution_context(self._machine)
Dale Curtis6eaaf232011-08-10 12:43:43 -0700208 os.chdir(self._server_job.resultdir)
Paul Pendleburye4afc772011-04-26 11:20:15 -0700209 if self._continuous_parsing:
210 self._server_job._parse_job += "/" + self._machine
211 self._server_job._using_parser = True
Paul Pendleburye4afc772011-04-26 11:20:15 -0700212 self._server_job.init_parser()
Paul Pendleburyf807c182011-04-05 11:24:34 -0700213
Paul Pendleburyf807c182011-04-05 11:24:34 -0700214 while True:
215 active_test = self.get_test()
216 if active_test is None:
217 break
218
Paul Pendleburyf807c182011-04-05 11:24:34 -0700219 logging.info('%s running %s', self._machine, active_test)
220 try:
Paul Pendlebury7739b432011-06-17 09:31:57 -0700221 active_test.run_test(self._client_at, self._results_dir,
222 self._server_job)
Paul Pendlebury92a90742011-05-25 11:54:34 -0700223 except error.AutoservError:
224 logging.exception('Autoserv error running "%s".', active_test)
225 except error.AutotestError:
226 logging.exception('Autotest error running "%s".', active_test)
Paul Pendleburyf807c182011-04-05 11:24:34 -0700227 except Exception:
228 logging.exception('Exception running test "%s".', active_test)
229 raise
230 finally:
231 self._test_queue.task_done()
232 self._tests_run += 1
233
Paul Pendleburye4afc772011-04-26 11:20:15 -0700234 if self._continuous_parsing:
235 self._server_job.cleanup_parser()
Paul Pendleburyf807c182011-04-05 11:24:34 -0700236 logging.info('%s completed %d tests.', self._machine, self._tests_run)