blob: c61130b674d5de36861ddb87b14b53d103db4210 [file] [log] [blame]
mbligh67647152008-11-19 00:18:14 +00001# Copyright Martin J. Bligh, Google Inc 2008
2# Released under the GPL v2
3
4"""
5This class allows you to communicate with the frontend to submit jobs etc
6It is designed for writing more sophisiticated server-side control files that
7can recursively add and manage other jobs.
8
9We turn the JSON dictionaries into real objects that are more idiomatic
10
mblighc31e4022008-12-11 19:32:30 +000011For docs, see:
12 http://autotest/afe/server/noauth/rpc/
13 http://autotest/new_tko/server/noauth/rpc/
14 http://docs.djangoproject.com/en/dev/ref/models/querysets/#queryset-api
mbligh67647152008-11-19 00:18:14 +000015"""
16
mbligh1f23f362008-12-22 14:46:12 +000017import os, time, traceback, re
mbligh67647152008-11-19 00:18:14 +000018import common
19from autotest_lib.frontend.afe import rpc_client_lib
mbligh37eceaa2008-12-15 22:56:37 +000020from autotest_lib.client.common_lib import global_config
mbligh67647152008-11-19 00:18:14 +000021from autotest_lib.client.common_lib import utils
mbligh4e576612008-12-22 14:56:36 +000022try:
23 from autotest_lib.server.site_common import site_utils as server_utils
24except:
25 from autotest_lib.server import utils as server_utils
26form_ntuples_from_machines = server_utils.form_ntuples_from_machines
mbligh67647152008-11-19 00:18:14 +000027
mbligh37eceaa2008-12-15 22:56:37 +000028GLOBAL_CONFIG = global_config.global_config
29DEFAULT_SERVER = 'autotest'
30
mbligh67647152008-11-19 00:18:14 +000031def dump_object(header, obj):
32 """
33 Standard way to print out the frontend objects (eg job, host, acl, label)
34 in a human-readable fashion for debugging
35 """
36 result = header + '\n'
37 for key in obj.hash:
38 if key == 'afe' or key == 'hash':
39 continue
40 result += '%20s: %s\n' % (key, obj.hash[key])
41 return result
42
43
mbligh5280e3b2008-12-22 14:39:28 +000044class RpcClient(object):
mbligh67647152008-11-19 00:18:14 +000045 """
mbligh451ede12009-02-12 21:54:03 +000046 Abstract RPC class for communicating with the autotest frontend
47 Inherited for both TKO and AFE uses.
mbligh67647152008-11-19 00:18:14 +000048
mbligh451ede12009-02-12 21:54:03 +000049 All the constructors go in the afe / tko class.
50 Manipulating methods go in the object classes themselves
mbligh67647152008-11-19 00:18:14 +000051 """
mbligh451ede12009-02-12 21:54:03 +000052 def __init__(self, path, user, server, print_log, debug):
mbligh67647152008-11-19 00:18:14 +000053 """
mbligh451ede12009-02-12 21:54:03 +000054 Create a cached instance of a connection to the frontend
mbligh67647152008-11-19 00:18:14 +000055
56 user: username to connect as
mbligh451ede12009-02-12 21:54:03 +000057 server: frontend server to connect to
mbligh67647152008-11-19 00:18:14 +000058 print_log: pring a logging message to stdout on every operation
59 debug: print out all RPC traffic
60 """
mblighc31e4022008-12-11 19:32:30 +000061 if not user:
62 user = os.environ.get('LOGNAME')
mbligh451ede12009-02-12 21:54:03 +000063 if not server:
mbligh475f7762009-01-30 00:34:04 +000064 if 'AUTOTEST_WEB' in os.environ:
mbligh451ede12009-02-12 21:54:03 +000065 server = os.environ['AUTOTEST_WEB']
mbligh475f7762009-01-30 00:34:04 +000066 else:
mbligh451ede12009-02-12 21:54:03 +000067 server = GLOBAL_CONFIG.get_config_value('SERVER', 'hostname',
68 default=DEFAULT_SERVER)
69 self.server = server
mbligh67647152008-11-19 00:18:14 +000070 self.user = user
71 self.print_log = print_log
72 self.debug = debug
73 headers = {'AUTHORIZATION' : self.user}
mbligh451ede12009-02-12 21:54:03 +000074 rpc_server = 'http://' + server + path
mbligh1354c9d2008-12-22 14:56:13 +000075 if debug:
76 print 'SERVER: %s' % rpc_server
77 print 'HEADERS: %s' % headers
mbligh67647152008-11-19 00:18:14 +000078 self.proxy = rpc_client_lib.get_proxy(rpc_server, headers=headers)
79
80
81 def run(self, call, **dargs):
82 """
83 Make a RPC call to the AFE server
84 """
85 rpc_call = getattr(self.proxy, call)
86 if self.debug:
87 print 'DEBUG: %s %s' % (call, dargs)
mbligh451ede12009-02-12 21:54:03 +000088 try:
89 return utils.strip_unicode(rpc_call(**dargs))
90 except Exception:
91 print 'FAILED RPC CALL: %s %s' % (call, dargs)
92 raise
mbligh67647152008-11-19 00:18:14 +000093
94
95 def log(self, message):
96 if self.print_log:
97 print message
98
99
mbligh5280e3b2008-12-22 14:39:28 +0000100class TKO(RpcClient):
mbligh451ede12009-02-12 21:54:03 +0000101 def __init__(self, user=None, server=None, print_log=True, debug=False):
mbligh5280e3b2008-12-22 14:39:28 +0000102 super(TKO, self).__init__('/new_tko/server/noauth/rpc/', user,
mbligh451ede12009-02-12 21:54:03 +0000103 server, print_log, debug)
mblighc31e4022008-12-11 19:32:30 +0000104
105
106 def get_status_counts(self, job, **data):
107 entries = self.run('get_status_counts',
mbligh451ede12009-02-12 21:54:03 +0000108 group_by=['hostname', 'test_name', 'reason'],
mblighc31e4022008-12-11 19:32:30 +0000109 job_tag__startswith='%s-' % job, **data)
mbligh5280e3b2008-12-22 14:39:28 +0000110 return [TestStatus(self, e) for e in entries['groups']]
mblighc31e4022008-12-11 19:32:30 +0000111
112
mbligh5280e3b2008-12-22 14:39:28 +0000113class AFE(RpcClient):
mbligh451ede12009-02-12 21:54:03 +0000114 def __init__(self, user=None, server=None, print_log=True, debug=False):
115 super(AFE, self).__init__('/afe/server/noauth/rpc/', user, server,
mblighc31e4022008-12-11 19:32:30 +0000116 print_log, debug)
117
118
mbligh67647152008-11-19 00:18:14 +0000119 def host_statuses(self, live=None):
mblighc2847b72009-03-25 19:32:20 +0000120 dead_statuses = ['Dead', 'Repair Failed', 'Repairing']
mbligh67647152008-11-19 00:18:14 +0000121 statuses = self.run('get_static_data')['host_statuses']
122 if live == True:
mblighc2847b72009-03-25 19:32:20 +0000123 return list(set(statuses) - set(dead_statuses))
mbligh67647152008-11-19 00:18:14 +0000124 if live == False:
125 return dead_statuses
126 else:
127 return statuses
128
129
130 def get_hosts(self, **dargs):
131 hosts = self.run('get_hosts', **dargs)
mbligh5280e3b2008-12-22 14:39:28 +0000132 return [Host(self, h) for h in hosts]
mbligh67647152008-11-19 00:18:14 +0000133
134
135 def create_host(self, hostname, **dargs):
mbligh54459c72009-01-21 19:26:44 +0000136 id = self.run('add_host', hostname=hostname, **dargs)
mbligh67647152008-11-19 00:18:14 +0000137 return self.get_hosts(id=id)[0]
138
139
140 def get_labels(self, **dargs):
141 labels = self.run('get_labels', **dargs)
mbligh5280e3b2008-12-22 14:39:28 +0000142 return [Label(self, l) for l in labels]
mbligh67647152008-11-19 00:18:14 +0000143
144
145 def create_label(self, name, **dargs):
mbligh54459c72009-01-21 19:26:44 +0000146 id = self.run('add_label', name=name, **dargs)
mbligh67647152008-11-19 00:18:14 +0000147 return self.get_labels(id=id)[0]
148
149
150 def get_acls(self, **dargs):
151 acls = self.run('get_acl_groups', **dargs)
mbligh5280e3b2008-12-22 14:39:28 +0000152 return [Acl(self, a) for a in acls]
mbligh67647152008-11-19 00:18:14 +0000153
154
155 def create_acl(self, name, **dargs):
mbligh54459c72009-01-21 19:26:44 +0000156 id = self.run('add_acl_group', name=name, **dargs)
mbligh67647152008-11-19 00:18:14 +0000157 return self.get_acls(id=id)[0]
158
159
mbligh54459c72009-01-21 19:26:44 +0000160 def get_users(self, **dargs):
161 users = self.run('get_users', **dargs)
162 return [User(self, u) for u in users]
163
164
mbligh1354c9d2008-12-22 14:56:13 +0000165 def generate_control_file(self, tests, **dargs):
166 ret = self.run('generate_control_file', tests=tests, **dargs)
167 return ControlFile(self, ret)
168
169
mbligh67647152008-11-19 00:18:14 +0000170 def get_jobs(self, summary=False, **dargs):
171 if summary:
172 jobs_data = self.run('get_jobs_summary', **dargs)
173 else:
174 jobs_data = self.run('get_jobs', **dargs)
mbligh5280e3b2008-12-22 14:39:28 +0000175 return [Job(self, j) for j in jobs_data]
mbligh67647152008-11-19 00:18:14 +0000176
177
178 def get_host_queue_entries(self, **data):
179 entries = self.run('get_host_queue_entries', **data)
mblighf9e35862009-02-26 01:03:11 +0000180 job_statuses = [JobStatus(self, e) for e in entries]
181 # filter job statuses that have either host or meta_host
182 return [status for status in job_statuses if (status.host or
183 status.meta_host)]
mbligh67647152008-11-19 00:18:14 +0000184
185
mblighb9db5162009-04-17 22:21:41 +0000186 def create_job_by_test(self, tests, kernel=None, use_container=False,
mbligh1354c9d2008-12-22 14:56:13 +0000187 **dargs):
mbligh67647152008-11-19 00:18:14 +0000188 """
189 Given a test name, fetch the appropriate control file from the server
mbligh4e576612008-12-22 14:56:36 +0000190 and submit it.
191
192 Returns a list of job objects
mbligh67647152008-11-19 00:18:14 +0000193 """
mblighb9db5162009-04-17 22:21:41 +0000194 assert ('hosts' in dargs or
195 'atomic_group_name' in dargs and 'synch_count' in dargs)
mbligh1354c9d2008-12-22 14:56:13 +0000196 control_file = self.generate_control_file(tests=tests, kernel=kernel,
197 use_container=use_container,
198 do_push_packages=True)
199 if control_file.is_server:
mbligh67647152008-11-19 00:18:14 +0000200 dargs['control_type'] = 'Server'
201 else:
202 dargs['control_type'] = 'Client'
203 dargs['dependencies'] = dargs.get('dependencies', []) + \
mbligh1354c9d2008-12-22 14:56:13 +0000204 control_file.dependencies
205 dargs['control_file'] = control_file.control_file
mblighb9db5162009-04-17 22:21:41 +0000206 dargs.setdefault('synch_count', control_file.synch_count)
207 if 'hosts' in dargs and len(dargs['hosts']) < dargs['synch_count']:
208 # will not be able to satisfy this request
mbligh38b09152009-04-28 18:34:25 +0000209 return None
210 return self.create_job(**dargs)
mbligh67647152008-11-19 00:18:14 +0000211
212
213 def create_job(self, control_file, name=' ', priority='Medium',
214 control_type='Client', **dargs):
215 id = self.run('create_job', name=name, priority=priority,
216 control_file=control_file, control_type=control_type, **dargs)
217 return self.get_jobs(id=id)[0]
218
219
mbligh1f23f362008-12-22 14:46:12 +0000220 def run_test_suites(self, pairings, kernel, kernel_label, priority='Medium',
221 wait=True, poll_interval=5, email_from=None,
mbligh7b312282009-01-07 16:45:43 +0000222 email_to=None, timeout=168):
mbligh5b618382008-12-03 15:24:01 +0000223 """
224 Run a list of test suites on a particular kernel.
225
226 Poll for them to complete, and return whether they worked or not.
227
228 pairings: list of MachineTestPairing objects to invoke
229 kernel: name of the kernel to run
230 kernel_label: label of the kernel to run
231 (<kernel-version> : <config> : <date>)
232 wait: boolean - wait for the results to come back?
233 poll_interval: interval between polling for job results (in minutes)
mbligh45ffc432008-12-09 23:35:17 +0000234 email_from: send notification email upon completion from here
235 email_from: send notification email upon completion to here
mbligh5b618382008-12-03 15:24:01 +0000236 """
237 jobs = []
238 for pairing in pairings:
mbligh38b09152009-04-28 18:34:25 +0000239 new_job = self.invoke_test(pairing, kernel, kernel_label, priority,
240 timeout=timeout)
241 if not new_job:
242 continue
243 new_job.notified = False
244 jobs.append(new_job)
mblighb9db5162009-04-17 22:21:41 +0000245 if not wait or not jobs:
mbligh5b618382008-12-03 15:24:01 +0000246 return
mbligh5280e3b2008-12-22 14:39:28 +0000247 tko = TKO()
mbligh5b618382008-12-03 15:24:01 +0000248 while True:
249 time.sleep(60 * poll_interval)
mbligh5280e3b2008-12-22 14:39:28 +0000250 result = self.poll_all_jobs(tko, jobs, email_from, email_to)
mbligh5b618382008-12-03 15:24:01 +0000251 if result is not None:
252 return result
253
254
mbligh45ffc432008-12-09 23:35:17 +0000255 def result_notify(self, job, email_from, email_to):
mbligh5b618382008-12-03 15:24:01 +0000256 """
mbligh45ffc432008-12-09 23:35:17 +0000257 Notify about the result of a job. Will always print, if email data
258 is provided, will send email for it as well.
259
260 job: job object to notify about
261 email_from: send notification email upon completion from here
262 email_from: send notification email upon completion to here
263 """
264 if job.result == True:
265 subject = 'Testing PASSED: '
266 else:
267 subject = 'Testing FAILED: '
268 subject += '%s : %s\n' % (job.name, job.id)
269 text = []
270 for platform in job.results_platform_map:
271 for status in job.results_platform_map[platform]:
272 if status == 'Total':
273 continue
mbligh451ede12009-02-12 21:54:03 +0000274 for host in job.results_platform_map[platform][status]:
275 text.append('%20s %10s %10s' % (platform, status, host))
276 if status == 'Failed':
277 for test_status in job.test_status[host].fail:
278 text.append('(%s, %s) : %s' % \
279 (host, test_status.test_name,
280 test_status.reason))
281 text.append('')
mbligh37eceaa2008-12-15 22:56:37 +0000282
mbligh451ede12009-02-12 21:54:03 +0000283 base_url = 'http://' + self.server
mbligh37eceaa2008-12-15 22:56:37 +0000284
285 params = ('columns=test',
286 'rows=machine_group',
287 "condition=tag~'%s-%%25'" % job.id,
288 'title=Report')
289 query_string = '&'.join(params)
mbligh451ede12009-02-12 21:54:03 +0000290 url = '%s/tko/compose_query.cgi?%s' % (base_url, query_string)
291 text.append(url + '\n')
292 url = '%s/afe/#tab_id=view_job&object_id=%s' % (base_url, job.id)
293 text.append(url + '\n')
mbligh37eceaa2008-12-15 22:56:37 +0000294
295 body = '\n'.join(text)
296 print '---------------------------------------------------'
297 print 'Subject: ', subject
mbligh45ffc432008-12-09 23:35:17 +0000298 print body
mbligh37eceaa2008-12-15 22:56:37 +0000299 print '---------------------------------------------------'
mbligh45ffc432008-12-09 23:35:17 +0000300 if email_from and email_to:
mbligh37eceaa2008-12-15 22:56:37 +0000301 print 'Sending email ...'
mbligh45ffc432008-12-09 23:35:17 +0000302 utils.send_email(email_from, email_to, subject, body)
303 print
mbligh37eceaa2008-12-15 22:56:37 +0000304
mbligh45ffc432008-12-09 23:35:17 +0000305
mbligh1354c9d2008-12-22 14:56:13 +0000306 def print_job_result(self, job):
307 """
308 Print the result of a single job.
309 job: a job object
310 """
311 if job.result is None:
312 print 'PENDING',
313 elif job.result == True:
314 print 'PASSED',
315 elif job.result == False:
316 print 'FAILED',
mbligh912c3f32009-03-25 19:31:30 +0000317 elif job.result == "Abort":
318 print 'ABORT',
mbligh1354c9d2008-12-22 14:56:13 +0000319 print ' %s : %s' % (job.id, job.name)
320
321
mbligh451ede12009-02-12 21:54:03 +0000322 def poll_all_jobs(self, tko, jobs, email_from=None, email_to=None):
mbligh45ffc432008-12-09 23:35:17 +0000323 """
324 Poll all jobs in a list.
325 jobs: list of job objects to poll
326 email_from: send notification email upon completion from here
327 email_from: send notification email upon completion to here
328
329 Returns:
mbligh5b618382008-12-03 15:24:01 +0000330 a) All complete successfully (return True)
331 b) One or more has failed (return False)
332 c) Cannot tell yet (return None)
333 """
mbligh45ffc432008-12-09 23:35:17 +0000334 results = []
mbligh5b618382008-12-03 15:24:01 +0000335 for job in jobs:
mbligh451ede12009-02-12 21:54:03 +0000336 job.result = self.poll_job_results(tko, job)
mbligh45ffc432008-12-09 23:35:17 +0000337 results.append(job.result)
338 if job.result is not None and not job.notified:
339 self.result_notify(job, email_from, email_to)
340 job.notified = True
341
mbligh1354c9d2008-12-22 14:56:13 +0000342 self.print_job_result(job)
mbligh45ffc432008-12-09 23:35:17 +0000343
344 if None in results:
345 return None
mbligh912c3f32009-03-25 19:31:30 +0000346 elif False in results or "Abort" in results:
mbligh45ffc432008-12-09 23:35:17 +0000347 return False
348 else:
349 return True
mbligh5b618382008-12-03 15:24:01 +0000350
351
mbligh1f23f362008-12-22 14:46:12 +0000352 def _included_platform(self, host, platforms):
353 """
354 See if host's platforms matches any of the patterns in the included
355 platforms list.
356 """
357 if not platforms:
358 return True # No filtering of platforms
359 for platform in platforms:
360 if re.search(platform, host.platform):
361 return True
362 return False
363
364
mbligh7b312282009-01-07 16:45:43 +0000365 def invoke_test(self, pairing, kernel, kernel_label, priority='Medium',
366 **dargs):
mbligh5b618382008-12-03 15:24:01 +0000367 """
368 Given a pairing of a control file to a machine label, find all machines
369 with that label, and submit that control file to them.
370
mbligh4e576612008-12-22 14:56:36 +0000371 Returns a list of job objects
mbligh5b618382008-12-03 15:24:01 +0000372 """
373 job_name = '%s : %s' % (pairing.machine_label, kernel_label)
374 hosts = self.get_hosts(multiple_labels=[pairing.machine_label])
mbligh1f23f362008-12-22 14:46:12 +0000375 platforms = pairing.platforms
376 hosts = [h for h in hosts if self._included_platform(h, platforms)]
mblighc2847b72009-03-25 19:32:20 +0000377 dead_statuses = self.host_statuses(live=False)
378 host_list = [h.hostname for h in hosts if h.status not in dead_statuses]
mbligh1f23f362008-12-22 14:46:12 +0000379 print 'HOSTS: %s' % host_list
mblighb9db5162009-04-17 22:21:41 +0000380 # TODO(ncrao): fix this when synch_count implements "at least N"
381 # semantics instead of "exactly N".
382 if pairing.atomic_group_sched:
383 if pairing.synch_count > 0:
384 dargs['synch_count'] = pairing.synch_count
385 else:
386 dargs['synch_count'] = len(host_list)
387 dargs['atomic_group_name'] = pairing.machine_label
388 else:
389 dargs['hosts'] = host_list
mbligh38b09152009-04-28 18:34:25 +0000390 new_job = self.create_job_by_test(name=job_name,
mbligh7b312282009-01-07 16:45:43 +0000391 dependencies=[pairing.machine_label],
392 tests=[pairing.control_file],
393 priority=priority,
mbligh7b312282009-01-07 16:45:43 +0000394 kernel=kernel,
395 use_container=pairing.container,
396 **dargs)
mbligh38b09152009-04-28 18:34:25 +0000397 if new_job:
mbligh4e576612008-12-22 14:56:36 +0000398 print 'Invoked test %s : %s' % (new_job.id, job_name)
mbligh38b09152009-04-28 18:34:25 +0000399 return new_job
mbligh5b618382008-12-03 15:24:01 +0000400
401
mblighb9db5162009-04-17 22:21:41 +0000402 def _job_test_results(self, tko, job, debug, tests=[]):
mbligh5b618382008-12-03 15:24:01 +0000403 """
mbligh5280e3b2008-12-22 14:39:28 +0000404 Retrieve test results for a job
mbligh5b618382008-12-03 15:24:01 +0000405 """
mbligh5280e3b2008-12-22 14:39:28 +0000406 job.test_status = {}
407 try:
408 test_statuses = tko.get_status_counts(job=job.id)
409 except Exception:
410 print "Ignoring exception on poll job; RPC interface is flaky"
411 traceback.print_exc()
412 return
413
414 for test_status in test_statuses:
mbligh7479a182009-01-07 16:46:24 +0000415 # SERVER_JOB is buggy, and often gives false failures. Ignore it.
416 if test_status.test_name == 'SERVER_JOB':
417 continue
mblighb9db5162009-04-17 22:21:41 +0000418 # if tests is not empty, restrict list of test_statuses to tests
419 if tests and test_status.test_name not in tests:
420 continue
mbligh451ede12009-02-12 21:54:03 +0000421 if debug:
422 print test_status
mbligh5280e3b2008-12-22 14:39:28 +0000423 hostname = test_status.hostname
424 if hostname not in job.test_status:
425 job.test_status[hostname] = TestResults()
426 job.test_status[hostname].add(test_status)
427
428
mbligh451ede12009-02-12 21:54:03 +0000429 def _job_results_platform_map(self, job, debug):
mblighc9e427e2009-04-28 18:35:06 +0000430 # Figure out which hosts passed / failed / aborted in a job
431 # Creates a 2-dimensional hash, stored as job.results_platform_map
432 # 1st index - platform type (string)
433 # 2nd index - Status (string)
434 # 'Completed' / 'Failed' / 'Aborted'
435 # Data indexed by this hash is a list of hostnames (text strings)
mbligh5280e3b2008-12-22 14:39:28 +0000436 job.results_platform_map = {}
mbligh5b618382008-12-03 15:24:01 +0000437 try:
mbligh45ffc432008-12-09 23:35:17 +0000438 job_statuses = self.get_host_queue_entries(job=job.id)
mbligh5b618382008-12-03 15:24:01 +0000439 except Exception:
440 print "Ignoring exception on poll job; RPC interface is flaky"
441 traceback.print_exc()
442 return None
mbligh5280e3b2008-12-22 14:39:28 +0000443
mbligh5b618382008-12-03 15:24:01 +0000444 platform_map = {}
mbligh5280e3b2008-12-22 14:39:28 +0000445 job.job_status = {}
mbligh451ede12009-02-12 21:54:03 +0000446 job.metahost_index = {}
mbligh5b618382008-12-03 15:24:01 +0000447 for job_status in job_statuses:
mblighc9e427e2009-04-28 18:35:06 +0000448 # This is basically "for each host / metahost in the job"
mbligh451ede12009-02-12 21:54:03 +0000449 if job_status.host:
450 hostname = job_status.host.hostname
451 else: # This is a metahost
452 metahost = job_status.meta_host
453 index = job.metahost_index.get(metahost, 1)
454 job.metahost_index[metahost] = index + 1
455 hostname = '%s.%s' % (metahost, index)
mbligh5280e3b2008-12-22 14:39:28 +0000456 job.job_status[hostname] = job_status.status
mbligh5b618382008-12-03 15:24:01 +0000457 status = job_status.status
mbligh451ede12009-02-12 21:54:03 +0000458 # Skip hosts that failed verify - that's a machine failure,
459 # not a job failure
460 if hostname in job.test_status:
461 verify_failed = False
462 for failure in job.test_status[hostname].fail:
463 if failure.test_name == 'verify':
464 verify_failed = True
465 break
466 if verify_failed:
467 continue
mblighc9e427e2009-04-28 18:35:06 +0000468 if hostname in job.test_status and job.test_status[hostname].fail:
469 # If the any tests failed in the job, we want to mark the
470 # job result as failed, overriding the default job status.
471 if status != "Aborted": # except if it's an aborted job
472 status = 'Failed'
mbligh451ede12009-02-12 21:54:03 +0000473 if job_status.host:
474 platform = job_status.host.platform
475 else: # This is a metahost
476 platform = job_status.meta_host
mbligh5b618382008-12-03 15:24:01 +0000477 if platform not in platform_map:
478 platform_map[platform] = {'Total' : [hostname]}
479 else:
480 platform_map[platform]['Total'].append(hostname)
481 new_host_list = platform_map[platform].get(status, []) + [hostname]
482 platform_map[platform][status] = new_host_list
mbligh45ffc432008-12-09 23:35:17 +0000483 job.results_platform_map = platform_map
mbligh5280e3b2008-12-22 14:39:28 +0000484
485
486 def poll_job_results(self, tko, job, debug=False):
487 """
488 Analyse all job results by platform, return:
mbligh5b618382008-12-03 15:24:01 +0000489
mbligh5280e3b2008-12-22 14:39:28 +0000490 False: if any platform has more than one failure
491 None: if any platform has more than one machine not yet Good.
492 True: if all platforms have at least all-but-one machines Good.
493 """
mbligh451ede12009-02-12 21:54:03 +0000494 self._job_test_results(tko, job, debug)
495 self._job_results_platform_map(job, debug)
mbligh5280e3b2008-12-22 14:39:28 +0000496
mbligh5b618382008-12-03 15:24:01 +0000497 good_platforms = []
mbligh912c3f32009-03-25 19:31:30 +0000498 failed_platforms = []
499 aborted_platforms = []
mbligh5b618382008-12-03 15:24:01 +0000500 unknown_platforms = []
mbligh5280e3b2008-12-22 14:39:28 +0000501 platform_map = job.results_platform_map
mbligh5b618382008-12-03 15:24:01 +0000502 for platform in platform_map:
503 total = len(platform_map[platform]['Total'])
504 completed = len(platform_map[platform].get('Completed', []))
mbligh912c3f32009-03-25 19:31:30 +0000505 failed = len(platform_map[platform].get('Failed', []))
506 aborted = len(platform_map[platform].get('Aborted', []))
507 if aborted > 1:
508 aborted_platforms.append(platform)
509 elif (failed * 2 >= total) or (failed > 1):
510 failed_platforms.append(platform)
mbligh451ede12009-02-12 21:54:03 +0000511 elif (completed >= 1) and (completed + 1 >= total):
mbligh5b618382008-12-03 15:24:01 +0000512 # if all or all but one are good, call the job good.
513 good_platforms.append(platform)
514 else:
515 unknown_platforms.append(platform)
516 detail = []
517 for status in platform_map[platform]:
518 if status == 'Total':
519 continue
520 detail.append('%s=%s' % (status,platform_map[platform][status]))
521 if debug:
522 print '%20s %d/%d %s' % (platform, completed, total,
523 ' '.join(detail))
524 print
525
mbligh912c3f32009-03-25 19:31:30 +0000526 if len(aborted_platforms) > 0:
mbligh5b618382008-12-03 15:24:01 +0000527 if debug:
mbligh912c3f32009-03-25 19:31:30 +0000528 print 'Result aborted - platforms: ' + ' '.join(aborted_platforms)
529 return "Abort"
530 if len(failed_platforms) > 0:
531 if debug:
532 print 'Result bad - platforms: ' + ' '.join(failed_platforms)
mbligh5b618382008-12-03 15:24:01 +0000533 return False
534 if len(unknown_platforms) > 0:
535 if debug:
536 platform_list = ' '.join(unknown_platforms)
537 print 'Result unknown - platforms: ', platform_list
538 return None
539 if debug:
540 platform_list = ' '.join(good_platforms)
541 print 'Result good - all platforms passed: ', platform_list
542 return True
543
544
mbligh5280e3b2008-12-22 14:39:28 +0000545class TestResults(object):
546 """
547 Container class used to hold the results of the tests for a job
548 """
549 def __init__(self):
550 self.good = []
551 self.fail = []
mbligh451ede12009-02-12 21:54:03 +0000552 self.pending = []
mbligh5280e3b2008-12-22 14:39:28 +0000553
554
555 def add(self, result):
mbligh451ede12009-02-12 21:54:03 +0000556 if result.complete_count > result.pass_count:
557 self.fail.append(result)
558 elif result.incomplete_count > 0:
559 self.pending.append(result)
mbligh5280e3b2008-12-22 14:39:28 +0000560 else:
mbligh451ede12009-02-12 21:54:03 +0000561 self.good.append(result)
mbligh5280e3b2008-12-22 14:39:28 +0000562
563
564class RpcObject(object):
mbligh67647152008-11-19 00:18:14 +0000565 """
566 Generic object used to construct python objects from rpc calls
567 """
568 def __init__(self, afe, hash):
569 self.afe = afe
570 self.hash = hash
571 self.__dict__.update(hash)
572
573
574 def __str__(self):
575 return dump_object(self.__repr__(), self)
576
577
mbligh1354c9d2008-12-22 14:56:13 +0000578class ControlFile(RpcObject):
579 """
580 AFE control file object
581
582 Fields: synch_count, dependencies, control_file, is_server
583 """
584 def __repr__(self):
585 return 'CONTROL FILE: %s' % self.control_file
586
587
mbligh5280e3b2008-12-22 14:39:28 +0000588class Label(RpcObject):
mbligh67647152008-11-19 00:18:14 +0000589 """
590 AFE label object
591
592 Fields:
593 name, invalid, platform, kernel_config, id, only_if_needed
594 """
595 def __repr__(self):
596 return 'LABEL: %s' % self.name
597
598
599 def add_hosts(self, hosts):
600 return self.afe.run('label_add_hosts', self.id, hosts)
601
602
603 def remove_hosts(self, hosts):
604 return self.afe.run('label_remove_hosts', self.id, hosts)
605
606
mbligh5280e3b2008-12-22 14:39:28 +0000607class Acl(RpcObject):
mbligh67647152008-11-19 00:18:14 +0000608 """
609 AFE acl object
610
611 Fields:
612 users, hosts, description, name, id
613 """
614 def __repr__(self):
615 return 'ACL: %s' % self.name
616
617
618 def add_hosts(self, hosts):
619 self.afe.log('Adding hosts %s to ACL %s' % (hosts, self.name))
620 return self.afe.run('acl_group_add_hosts', self.id, hosts)
621
622
623 def remove_hosts(self, hosts):
624 self.afe.log('Removing hosts %s from ACL %s' % (hosts, self.name))
625 return self.afe.run('acl_group_remove_hosts', self.id, hosts)
626
627
mbligh54459c72009-01-21 19:26:44 +0000628 def add_users(self, users):
629 self.afe.log('Adding users %s to ACL %s' % (users, self.name))
630 return self.afe.run('acl_group_add_users', id=self.name, users=users)
631
632
mbligh5280e3b2008-12-22 14:39:28 +0000633class Job(RpcObject):
mbligh67647152008-11-19 00:18:14 +0000634 """
635 AFE job object
636
637 Fields:
638 name, control_file, control_type, synch_count, reboot_before,
639 run_verify, priority, email_list, created_on, dependencies,
640 timeout, owner, reboot_after, id
641 """
642 def __repr__(self):
643 return 'JOB: %s' % self.id
644
645
mbligh5280e3b2008-12-22 14:39:28 +0000646class JobStatus(RpcObject):
mbligh67647152008-11-19 00:18:14 +0000647 """
648 AFE job_status object
649
650 Fields:
651 status, complete, deleted, meta_host, host, active, execution_subdir, id
652 """
653 def __init__(self, afe, hash):
654 # This should call super
655 self.afe = afe
656 self.hash = hash
657 self.__dict__.update(hash)
mbligh5280e3b2008-12-22 14:39:28 +0000658 self.job = Job(afe, self.job)
mbligh67647152008-11-19 00:18:14 +0000659 if self.host:
mblighf9e35862009-02-26 01:03:11 +0000660 # get list of hosts from AFE; if a host is not present in autotest
661 # anymore, this returns an empty list.
662 afe_hosts = afe.get_hosts(hostname=self.host['hostname'])
663 if len(afe_hosts):
664 # host present, assign it!
665 self.host = afe_hosts[0]
666 else:
667 # AFE does not contain info anymore, set host to None
668 self.host = None
mbligh67647152008-11-19 00:18:14 +0000669
670
671 def __repr__(self):
mbligh451ede12009-02-12 21:54:03 +0000672 if self.host and self.host.hostname:
673 hostname = self.host.hostname
674 else:
675 hostname = 'None'
676 return 'JOB STATUS: %s-%s' % (self.job.id, hostname)
mbligh67647152008-11-19 00:18:14 +0000677
678
mbligh5280e3b2008-12-22 14:39:28 +0000679class Host(RpcObject):
mbligh67647152008-11-19 00:18:14 +0000680 """
681 AFE host object
682
683 Fields:
684 status, lock_time, locked_by, locked, hostname, invalid,
685 synch_id, labels, platform, protection, dirty, id
686 """
687 def __repr__(self):
688 return 'HOST OBJECT: %s' % self.hostname
689
690
691 def show(self):
692 labels = list(set(self.labels) - set([self.platform]))
693 print '%-6s %-7s %-7s %-16s %s' % (self.hostname, self.status,
694 self.locked, self.platform,
695 ', '.join(labels))
696
697
mbligh54459c72009-01-21 19:26:44 +0000698 def delete(self):
699 return self.afe.run('delete_host', id=self.id)
700
701
mbligh6463c4b2009-01-30 00:33:37 +0000702 def modify(self, **dargs):
703 return self.afe.run('modify_host', id=self.id, **dargs)
704
705
mbligh67647152008-11-19 00:18:14 +0000706 def get_acls(self):
707 return self.afe.get_acls(hosts__hostname=self.hostname)
708
709
710 def add_acl(self, acl_name):
711 self.afe.log('Adding ACL %s to host %s' % (acl_name, self.hostname))
712 return self.afe.run('acl_group_add_hosts', id=acl_name,
713 hosts=[self.hostname])
714
715
716 def remove_acl(self, acl_name):
717 self.afe.log('Removing ACL %s from host %s' % (acl_name, self.hostname))
718 return self.afe.run('acl_group_remove_hosts', id=acl_name,
719 hosts=[self.hostname])
720
721
722 def get_labels(self):
723 return self.afe.get_labels(host__hostname__in=[self.hostname])
724
725
726 def add_labels(self, labels):
727 self.afe.log('Adding labels %s to host %s' % (labels, self.hostname))
728 return self.afe.run('host_add_labels', id=self.id, labels=labels)
729
730
731 def remove_labels(self, labels):
732 self.afe.log('Removing labels %s from host %s' % (labels,self.hostname))
733 return self.afe.run('host_remove_labels', id=self.id, labels=labels)
mbligh5b618382008-12-03 15:24:01 +0000734
735
mbligh54459c72009-01-21 19:26:44 +0000736class User(RpcObject):
737 def __repr__(self):
738 return 'USER: %s' % self.login
739
740
mbligh5280e3b2008-12-22 14:39:28 +0000741class TestStatus(RpcObject):
mblighc31e4022008-12-11 19:32:30 +0000742 """
743 TKO test status object
744
745 Fields:
746 test_idx, hostname, testname, id
747 complete_count, incomplete_count, group_count, pass_count
748 """
749 def __repr__(self):
750 return 'TEST STATUS: %s' % self.id
751
752
mbligh5b618382008-12-03 15:24:01 +0000753class MachineTestPairing(object):
754 """
755 Object representing the pairing of a machine label with a control file
mbligh1f23f362008-12-22 14:46:12 +0000756
757 machine_label: use machines from this label
758 control_file: use this control file (by name in the frontend)
759 platforms: list of rexeps to filter platforms by. [] => no filtering
mbligh5b618382008-12-03 15:24:01 +0000760 """
mbligh1354c9d2008-12-22 14:56:13 +0000761 def __init__(self, machine_label, control_file, platforms=[],
mblighb9db5162009-04-17 22:21:41 +0000762 container=False, atomic_group_sched=False, synch_count=0):
mbligh5b618382008-12-03 15:24:01 +0000763 self.machine_label = machine_label
764 self.control_file = control_file
mbligh1f23f362008-12-22 14:46:12 +0000765 self.platforms = platforms
mbligh1354c9d2008-12-22 14:56:13 +0000766 self.container = container
mblighb9db5162009-04-17 22:21:41 +0000767 self.atomic_group_sched = atomic_group_sched
768 self.synch_count = synch_count
mbligh1354c9d2008-12-22 14:56:13 +0000769
770
771 def __repr__(self):
772 return '%s %s %s %s' % (self.machine_label, self.control_file,
773 self.platforms, self.container)