blob: 359ab99319eb243848d65dc4408a9866b5afa9f0 [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 +000031
32def dump_object(header, obj):
33 """
34 Standard way to print out the frontend objects (eg job, host, acl, label)
35 in a human-readable fashion for debugging
36 """
37 result = header + '\n'
38 for key in obj.hash:
39 if key == 'afe' or key == 'hash':
40 continue
41 result += '%20s: %s\n' % (key, obj.hash[key])
42 return result
43
44
mbligh5280e3b2008-12-22 14:39:28 +000045class RpcClient(object):
mbligh67647152008-11-19 00:18:14 +000046 """
mbligh451ede12009-02-12 21:54:03 +000047 Abstract RPC class for communicating with the autotest frontend
48 Inherited for both TKO and AFE uses.
mbligh67647152008-11-19 00:18:14 +000049
mbligh451ede12009-02-12 21:54:03 +000050 All the constructors go in the afe / tko class.
51 Manipulating methods go in the object classes themselves
mbligh67647152008-11-19 00:18:14 +000052 """
mbligh451ede12009-02-12 21:54:03 +000053 def __init__(self, path, user, server, print_log, debug):
mbligh67647152008-11-19 00:18:14 +000054 """
mbligh451ede12009-02-12 21:54:03 +000055 Create a cached instance of a connection to the frontend
mbligh67647152008-11-19 00:18:14 +000056
57 user: username to connect as
mbligh451ede12009-02-12 21:54:03 +000058 server: frontend server to connect to
mbligh67647152008-11-19 00:18:14 +000059 print_log: pring a logging message to stdout on every operation
60 debug: print out all RPC traffic
61 """
mblighc31e4022008-12-11 19:32:30 +000062 if not user:
63 user = os.environ.get('LOGNAME')
mbligh451ede12009-02-12 21:54:03 +000064 if not server:
mbligh475f7762009-01-30 00:34:04 +000065 if 'AUTOTEST_WEB' in os.environ:
mbligh451ede12009-02-12 21:54:03 +000066 server = os.environ['AUTOTEST_WEB']
mbligh475f7762009-01-30 00:34:04 +000067 else:
mbligh451ede12009-02-12 21:54:03 +000068 server = GLOBAL_CONFIG.get_config_value('SERVER', 'hostname',
69 default=DEFAULT_SERVER)
70 self.server = server
mbligh67647152008-11-19 00:18:14 +000071 self.user = user
72 self.print_log = print_log
73 self.debug = debug
74 headers = {'AUTHORIZATION' : self.user}
mbligh451ede12009-02-12 21:54:03 +000075 rpc_server = 'http://' + server + path
mbligh1354c9d2008-12-22 14:56:13 +000076 if debug:
77 print 'SERVER: %s' % rpc_server
78 print 'HEADERS: %s' % headers
mbligh67647152008-11-19 00:18:14 +000079 self.proxy = rpc_client_lib.get_proxy(rpc_server, headers=headers)
80
81
82 def run(self, call, **dargs):
83 """
84 Make a RPC call to the AFE server
85 """
86 rpc_call = getattr(self.proxy, call)
87 if self.debug:
88 print 'DEBUG: %s %s' % (call, dargs)
mbligh451ede12009-02-12 21:54:03 +000089 try:
90 return utils.strip_unicode(rpc_call(**dargs))
91 except Exception:
92 print 'FAILED RPC CALL: %s %s' % (call, dargs)
93 raise
mbligh67647152008-11-19 00:18:14 +000094
95
96 def log(self, message):
97 if self.print_log:
98 print message
99
100
mbligh5280e3b2008-12-22 14:39:28 +0000101class TKO(RpcClient):
mbligh451ede12009-02-12 21:54:03 +0000102 def __init__(self, user=None, server=None, print_log=True, debug=False):
mbligh5280e3b2008-12-22 14:39:28 +0000103 super(TKO, self).__init__('/new_tko/server/noauth/rpc/', user,
mbligh451ede12009-02-12 21:54:03 +0000104 server, print_log, debug)
mblighc31e4022008-12-11 19:32:30 +0000105
106
107 def get_status_counts(self, job, **data):
108 entries = self.run('get_status_counts',
mbligh451ede12009-02-12 21:54:03 +0000109 group_by=['hostname', 'test_name', 'reason'],
mblighc31e4022008-12-11 19:32:30 +0000110 job_tag__startswith='%s-' % job, **data)
mbligh5280e3b2008-12-22 14:39:28 +0000111 return [TestStatus(self, e) for e in entries['groups']]
mblighc31e4022008-12-11 19:32:30 +0000112
113
mbligh5280e3b2008-12-22 14:39:28 +0000114class AFE(RpcClient):
mbligh451ede12009-02-12 21:54:03 +0000115 def __init__(self, user=None, server=None, print_log=True, debug=False):
116 super(AFE, self).__init__('/afe/server/noauth/rpc/', user, server,
mblighc31e4022008-12-11 19:32:30 +0000117 print_log, debug)
118
119
mbligh67647152008-11-19 00:18:14 +0000120 def host_statuses(self, live=None):
121 dead_statuses = ['Dead', 'Repair Failed']
122 statuses = self.run('get_static_data')['host_statuses']
123 if live == True:
124 return list(set(statuses) - set(['Dead', 'Repair Failed']))
125 if live == False:
126 return dead_statuses
127 else:
128 return statuses
129
130
131 def get_hosts(self, **dargs):
132 hosts = self.run('get_hosts', **dargs)
mbligh5280e3b2008-12-22 14:39:28 +0000133 return [Host(self, h) for h in hosts]
mbligh67647152008-11-19 00:18:14 +0000134
135
136 def create_host(self, hostname, **dargs):
mbligh54459c72009-01-21 19:26:44 +0000137 id = self.run('add_host', hostname=hostname, **dargs)
mbligh67647152008-11-19 00:18:14 +0000138 return self.get_hosts(id=id)[0]
139
140
141 def get_labels(self, **dargs):
142 labels = self.run('get_labels', **dargs)
mbligh5280e3b2008-12-22 14:39:28 +0000143 return [Label(self, l) for l in labels]
mbligh67647152008-11-19 00:18:14 +0000144
145
146 def create_label(self, name, **dargs):
mbligh54459c72009-01-21 19:26:44 +0000147 id = self.run('add_label', name=name, **dargs)
mbligh67647152008-11-19 00:18:14 +0000148 return self.get_labels(id=id)[0]
149
150
151 def get_acls(self, **dargs):
152 acls = self.run('get_acl_groups', **dargs)
mbligh5280e3b2008-12-22 14:39:28 +0000153 return [Acl(self, a) for a in acls]
mbligh67647152008-11-19 00:18:14 +0000154
155
156 def create_acl(self, name, **dargs):
mbligh54459c72009-01-21 19:26:44 +0000157 id = self.run('add_acl_group', name=name, **dargs)
mbligh67647152008-11-19 00:18:14 +0000158 return self.get_acls(id=id)[0]
159
160
mbligh54459c72009-01-21 19:26:44 +0000161 def get_users(self, **dargs):
162 users = self.run('get_users', **dargs)
163 return [User(self, u) for u in users]
164
165
mbligh1354c9d2008-12-22 14:56:13 +0000166 def generate_control_file(self, tests, **dargs):
167 ret = self.run('generate_control_file', tests=tests, **dargs)
168 return ControlFile(self, ret)
169
170
mbligh67647152008-11-19 00:18:14 +0000171 def get_jobs(self, summary=False, **dargs):
172 if summary:
173 jobs_data = self.run('get_jobs_summary', **dargs)
174 else:
175 jobs_data = self.run('get_jobs', **dargs)
mbligh5280e3b2008-12-22 14:39:28 +0000176 return [Job(self, j) for j in jobs_data]
mbligh67647152008-11-19 00:18:14 +0000177
178
179 def get_host_queue_entries(self, **data):
180 entries = self.run('get_host_queue_entries', **data)
mblighf9e35862009-02-26 01:03:11 +0000181 job_statuses = [JobStatus(self, e) for e in entries]
182 # filter job statuses that have either host or meta_host
183 return [status for status in job_statuses if (status.host or
184 status.meta_host)]
mbligh67647152008-11-19 00:18:14 +0000185
186
mbligh4e576612008-12-22 14:56:36 +0000187 def create_job_by_test(self, tests, hosts, kernel=None, use_container=False,
mbligh1354c9d2008-12-22 14:56:13 +0000188 **dargs):
mbligh67647152008-11-19 00:18:14 +0000189 """
190 Given a test name, fetch the appropriate control file from the server
mbligh4e576612008-12-22 14:56:36 +0000191 and submit it.
192
193 Returns a list of job objects
mbligh67647152008-11-19 00:18:14 +0000194 """
mbligh1354c9d2008-12-22 14:56:13 +0000195 control_file = self.generate_control_file(tests=tests, kernel=kernel,
196 use_container=use_container,
197 do_push_packages=True)
198 if control_file.is_server:
mbligh67647152008-11-19 00:18:14 +0000199 dargs['control_type'] = 'Server'
200 else:
201 dargs['control_type'] = 'Client'
202 dargs['dependencies'] = dargs.get('dependencies', []) + \
mbligh1354c9d2008-12-22 14:56:13 +0000203 control_file.dependencies
204 dargs['control_file'] = control_file.control_file
mbligh2a894102009-02-26 00:48:58 +0000205 if 'synch_count' not in dargs:
mblighb1ce3b32009-02-17 16:00:50 +0000206 dargs['synch_count'] = control_file.synch_count
mbligh2a894102009-02-26 00:48:58 +0000207 else:
208 control_file.synch_count = dargs['synch_count']
mbligh4e576612008-12-22 14:56:36 +0000209 jobs = []
210 if control_file.synch_count > 1:
211 # We don't trust the scheduler to do the groupings for us.
212 synch_count = control_file.synch_count
213 (pairs, failures) = form_ntuples_from_machines(hosts, synch_count)
214 for machines in pairs:
215 jobs.append(self.create_job(hosts=machines, **dargs))
216 else:
217 jobs.append(self.create_job(hosts=hosts, **dargs))
218 return jobs
mbligh67647152008-11-19 00:18:14 +0000219
220
221 def create_job(self, control_file, name=' ', priority='Medium',
222 control_type='Client', **dargs):
223 id = self.run('create_job', name=name, priority=priority,
224 control_file=control_file, control_type=control_type, **dargs)
225 return self.get_jobs(id=id)[0]
226
227
mbligh1f23f362008-12-22 14:46:12 +0000228 def run_test_suites(self, pairings, kernel, kernel_label, priority='Medium',
229 wait=True, poll_interval=5, email_from=None,
mbligh7b312282009-01-07 16:45:43 +0000230 email_to=None, timeout=168):
mbligh5b618382008-12-03 15:24:01 +0000231 """
232 Run a list of test suites on a particular kernel.
233
234 Poll for them to complete, and return whether they worked or not.
235
236 pairings: list of MachineTestPairing objects to invoke
237 kernel: name of the kernel to run
238 kernel_label: label of the kernel to run
239 (<kernel-version> : <config> : <date>)
240 wait: boolean - wait for the results to come back?
241 poll_interval: interval between polling for job results (in minutes)
mbligh45ffc432008-12-09 23:35:17 +0000242 email_from: send notification email upon completion from here
243 email_from: send notification email upon completion to here
mbligh5b618382008-12-03 15:24:01 +0000244 """
245 jobs = []
246 for pairing in pairings:
mbligh7b312282009-01-07 16:45:43 +0000247 new_jobs = self.invoke_test(pairing, kernel, kernel_label, priority,
248 timeout=timeout)
mbligh4e576612008-12-22 14:56:36 +0000249 for job in new_jobs:
250 job.notified = False
251 jobs += new_jobs
mbligh5280e3b2008-12-22 14:39:28 +0000252 # disabled - this is just for debugging: mbligh
253 # if email_from and email_to:
254 # subject = 'Testing started: %s : %s' % (job.name, job.id)
255 # utils.send_email(email_from, email_to, subject, subject)
mbligh5b618382008-12-03 15:24:01 +0000256 if not wait:
257 return
mbligh5280e3b2008-12-22 14:39:28 +0000258 tko = TKO()
mbligh5b618382008-12-03 15:24:01 +0000259 while True:
260 time.sleep(60 * poll_interval)
mbligh5280e3b2008-12-22 14:39:28 +0000261 result = self.poll_all_jobs(tko, jobs, email_from, email_to)
mbligh5b618382008-12-03 15:24:01 +0000262 if result is not None:
263 return result
264
265
mbligh45ffc432008-12-09 23:35:17 +0000266 def result_notify(self, job, email_from, email_to):
mbligh5b618382008-12-03 15:24:01 +0000267 """
mbligh45ffc432008-12-09 23:35:17 +0000268 Notify about the result of a job. Will always print, if email data
269 is provided, will send email for it as well.
270
271 job: job object to notify about
272 email_from: send notification email upon completion from here
273 email_from: send notification email upon completion to here
274 """
275 if job.result == True:
276 subject = 'Testing PASSED: '
277 else:
278 subject = 'Testing FAILED: '
279 subject += '%s : %s\n' % (job.name, job.id)
280 text = []
281 for platform in job.results_platform_map:
282 for status in job.results_platform_map[platform]:
283 if status == 'Total':
284 continue
mbligh451ede12009-02-12 21:54:03 +0000285 for host in job.results_platform_map[platform][status]:
286 text.append('%20s %10s %10s' % (platform, status, host))
287 if status == 'Failed':
288 for test_status in job.test_status[host].fail:
289 text.append('(%s, %s) : %s' % \
290 (host, test_status.test_name,
291 test_status.reason))
292 text.append('')
mbligh37eceaa2008-12-15 22:56:37 +0000293
mbligh451ede12009-02-12 21:54:03 +0000294 base_url = 'http://' + self.server
mbligh37eceaa2008-12-15 22:56:37 +0000295
296 params = ('columns=test',
297 'rows=machine_group',
298 "condition=tag~'%s-%%25'" % job.id,
299 'title=Report')
300 query_string = '&'.join(params)
mbligh451ede12009-02-12 21:54:03 +0000301 url = '%s/tko/compose_query.cgi?%s' % (base_url, query_string)
302 text.append(url + '\n')
303 url = '%s/afe/#tab_id=view_job&object_id=%s' % (base_url, job.id)
304 text.append(url + '\n')
mbligh37eceaa2008-12-15 22:56:37 +0000305
306 body = '\n'.join(text)
307 print '---------------------------------------------------'
308 print 'Subject: ', subject
mbligh45ffc432008-12-09 23:35:17 +0000309 print body
mbligh37eceaa2008-12-15 22:56:37 +0000310 print '---------------------------------------------------'
mbligh45ffc432008-12-09 23:35:17 +0000311 if email_from and email_to:
mbligh37eceaa2008-12-15 22:56:37 +0000312 print 'Sending email ...'
mbligh45ffc432008-12-09 23:35:17 +0000313 utils.send_email(email_from, email_to, subject, body)
314 print
mbligh37eceaa2008-12-15 22:56:37 +0000315
mbligh45ffc432008-12-09 23:35:17 +0000316
mbligh1354c9d2008-12-22 14:56:13 +0000317 def print_job_result(self, job):
318 """
319 Print the result of a single job.
320 job: a job object
321 """
322 if job.result is None:
323 print 'PENDING',
324 elif job.result == True:
325 print 'PASSED',
326 elif job.result == False:
327 print 'FAILED',
328 print ' %s : %s' % (job.id, job.name)
329
330
mbligh451ede12009-02-12 21:54:03 +0000331 def poll_all_jobs(self, tko, jobs, email_from=None, email_to=None):
mbligh45ffc432008-12-09 23:35:17 +0000332 """
333 Poll all jobs in a list.
334 jobs: list of job objects to poll
335 email_from: send notification email upon completion from here
336 email_from: send notification email upon completion to here
337
338 Returns:
mbligh5b618382008-12-03 15:24:01 +0000339 a) All complete successfully (return True)
340 b) One or more has failed (return False)
341 c) Cannot tell yet (return None)
342 """
mbligh45ffc432008-12-09 23:35:17 +0000343 results = []
mbligh5b618382008-12-03 15:24:01 +0000344 for job in jobs:
mbligh451ede12009-02-12 21:54:03 +0000345 job.result = self.poll_job_results(tko, job)
mbligh45ffc432008-12-09 23:35:17 +0000346 results.append(job.result)
347 if job.result is not None and not job.notified:
348 self.result_notify(job, email_from, email_to)
349 job.notified = True
350
mbligh1354c9d2008-12-22 14:56:13 +0000351 self.print_job_result(job)
mbligh45ffc432008-12-09 23:35:17 +0000352
353 if None in results:
354 return None
355 elif False in results:
356 return False
357 else:
358 return True
mbligh5b618382008-12-03 15:24:01 +0000359
360
mbligh1f23f362008-12-22 14:46:12 +0000361 def _included_platform(self, host, platforms):
362 """
363 See if host's platforms matches any of the patterns in the included
364 platforms list.
365 """
366 if not platforms:
367 return True # No filtering of platforms
368 for platform in platforms:
369 if re.search(platform, host.platform):
370 return True
371 return False
372
373
mbligh7b312282009-01-07 16:45:43 +0000374 def invoke_test(self, pairing, kernel, kernel_label, priority='Medium',
375 **dargs):
mbligh5b618382008-12-03 15:24:01 +0000376 """
377 Given a pairing of a control file to a machine label, find all machines
378 with that label, and submit that control file to them.
379
mbligh4e576612008-12-22 14:56:36 +0000380 Returns a list of job objects
mbligh5b618382008-12-03 15:24:01 +0000381 """
382 job_name = '%s : %s' % (pairing.machine_label, kernel_label)
383 hosts = self.get_hosts(multiple_labels=[pairing.machine_label])
mbligh1f23f362008-12-22 14:46:12 +0000384 platforms = pairing.platforms
385 hosts = [h for h in hosts if self._included_platform(h, platforms)]
mbligh45ffc432008-12-09 23:35:17 +0000386 host_list = [h.hostname for h in hosts if h.status != 'Repair Failed']
mbligh1f23f362008-12-22 14:46:12 +0000387 print 'HOSTS: %s' % host_list
mbligh2a894102009-02-26 00:48:58 +0000388 if pairing.synch_job:
389 dargs['synch_count'] = len(host_list)
mbligh4e576612008-12-22 14:56:36 +0000390 new_jobs = 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,
394 hosts=host_list,
395 kernel=kernel,
396 use_container=pairing.container,
397 **dargs)
mbligh4e576612008-12-22 14:56:36 +0000398 for new_job in new_jobs:
399 print 'Invoked test %s : %s' % (new_job.id, job_name)
400 return new_jobs
mbligh5b618382008-12-03 15:24:01 +0000401
402
mbligh451ede12009-02-12 21:54:03 +0000403 def _job_test_results(self, tko, job, debug):
mbligh5b618382008-12-03 15:24:01 +0000404 """
mbligh5280e3b2008-12-22 14:39:28 +0000405 Retrieve test results for a job
mbligh5b618382008-12-03 15:24:01 +0000406 """
mbligh5280e3b2008-12-22 14:39:28 +0000407 job.test_status = {}
408 try:
409 test_statuses = tko.get_status_counts(job=job.id)
410 except Exception:
411 print "Ignoring exception on poll job; RPC interface is flaky"
412 traceback.print_exc()
413 return
414
415 for test_status in test_statuses:
mbligh7479a182009-01-07 16:46:24 +0000416 # SERVER_JOB is buggy, and often gives false failures. Ignore it.
417 if test_status.test_name == 'SERVER_JOB':
418 continue
mbligh451ede12009-02-12 21:54:03 +0000419 if debug:
420 print test_status
mbligh5280e3b2008-12-22 14:39:28 +0000421 hostname = test_status.hostname
422 if hostname not in job.test_status:
423 job.test_status[hostname] = TestResults()
424 job.test_status[hostname].add(test_status)
425
426
mbligh451ede12009-02-12 21:54:03 +0000427 def _job_results_platform_map(self, job, debug):
mbligh5280e3b2008-12-22 14:39:28 +0000428 job.results_platform_map = {}
mbligh5b618382008-12-03 15:24:01 +0000429 try:
mbligh45ffc432008-12-09 23:35:17 +0000430 job_statuses = self.get_host_queue_entries(job=job.id)
mbligh5b618382008-12-03 15:24:01 +0000431 except Exception:
432 print "Ignoring exception on poll job; RPC interface is flaky"
433 traceback.print_exc()
434 return None
mbligh5280e3b2008-12-22 14:39:28 +0000435
mbligh5b618382008-12-03 15:24:01 +0000436 platform_map = {}
mbligh5280e3b2008-12-22 14:39:28 +0000437 job.job_status = {}
mbligh451ede12009-02-12 21:54:03 +0000438 job.metahost_index = {}
mbligh5b618382008-12-03 15:24:01 +0000439 for job_status in job_statuses:
mbligh451ede12009-02-12 21:54:03 +0000440 if job_status.host:
441 hostname = job_status.host.hostname
442 else: # This is a metahost
443 metahost = job_status.meta_host
444 index = job.metahost_index.get(metahost, 1)
445 job.metahost_index[metahost] = index + 1
446 hostname = '%s.%s' % (metahost, index)
mbligh5280e3b2008-12-22 14:39:28 +0000447 job.job_status[hostname] = job_status.status
mbligh5b618382008-12-03 15:24:01 +0000448 status = job_status.status
mbligh451ede12009-02-12 21:54:03 +0000449 # Skip hosts that failed verify - that's a machine failure,
450 # not a job failure
451 if hostname in job.test_status:
452 verify_failed = False
453 for failure in job.test_status[hostname].fail:
454 if failure.test_name == 'verify':
455 verify_failed = True
456 break
457 if verify_failed:
458 continue
mbligh5280e3b2008-12-22 14:39:28 +0000459 if hostname in job.test_status and job.test_status[hostname].fail:
460 # Job status doesn't reflect failed tests, override that
461 status = 'Failed'
mbligh451ede12009-02-12 21:54:03 +0000462 if job_status.host:
463 platform = job_status.host.platform
464 else: # This is a metahost
465 platform = job_status.meta_host
mbligh5b618382008-12-03 15:24:01 +0000466 if platform not in platform_map:
467 platform_map[platform] = {'Total' : [hostname]}
468 else:
469 platform_map[platform]['Total'].append(hostname)
470 new_host_list = platform_map[platform].get(status, []) + [hostname]
471 platform_map[platform][status] = new_host_list
mbligh45ffc432008-12-09 23:35:17 +0000472 job.results_platform_map = platform_map
mbligh5280e3b2008-12-22 14:39:28 +0000473
474
475 def poll_job_results(self, tko, job, debug=False):
476 """
477 Analyse all job results by platform, return:
mbligh5b618382008-12-03 15:24:01 +0000478
mbligh5280e3b2008-12-22 14:39:28 +0000479 False: if any platform has more than one failure
480 None: if any platform has more than one machine not yet Good.
481 True: if all platforms have at least all-but-one machines Good.
482 """
mbligh451ede12009-02-12 21:54:03 +0000483 self._job_test_results(tko, job, debug)
484 self._job_results_platform_map(job, debug)
mbligh5280e3b2008-12-22 14:39:28 +0000485
mbligh5b618382008-12-03 15:24:01 +0000486 good_platforms = []
487 bad_platforms = []
488 unknown_platforms = []
mbligh5280e3b2008-12-22 14:39:28 +0000489 platform_map = job.results_platform_map
mbligh5b618382008-12-03 15:24:01 +0000490 for platform in platform_map:
491 total = len(platform_map[platform]['Total'])
492 completed = len(platform_map[platform].get('Completed', []))
mbligh451ede12009-02-12 21:54:03 +0000493 failed = len(platform_map[platform].get('Failed', [])) + \
494 len(platform_map[platform].get('Aborted', []))
495 if (failed * 2 >= total) or (failed > 1):
mbligh5b618382008-12-03 15:24:01 +0000496 bad_platforms.append(platform)
mbligh451ede12009-02-12 21:54:03 +0000497 elif (completed >= 1) and (completed + 1 >= total):
mbligh5b618382008-12-03 15:24:01 +0000498 # if all or all but one are good, call the job good.
499 good_platforms.append(platform)
500 else:
501 unknown_platforms.append(platform)
502 detail = []
503 for status in platform_map[platform]:
504 if status == 'Total':
505 continue
506 detail.append('%s=%s' % (status,platform_map[platform][status]))
507 if debug:
508 print '%20s %d/%d %s' % (platform, completed, total,
509 ' '.join(detail))
510 print
511
512 if len(bad_platforms) > 0:
513 if debug:
514 print 'Result bad - platforms: ' + ' '.join(bad_platforms)
515 return False
516 if len(unknown_platforms) > 0:
517 if debug:
518 platform_list = ' '.join(unknown_platforms)
519 print 'Result unknown - platforms: ', platform_list
520 return None
521 if debug:
522 platform_list = ' '.join(good_platforms)
523 print 'Result good - all platforms passed: ', platform_list
524 return True
525
526
mbligh5280e3b2008-12-22 14:39:28 +0000527class TestResults(object):
528 """
529 Container class used to hold the results of the tests for a job
530 """
531 def __init__(self):
532 self.good = []
533 self.fail = []
mbligh451ede12009-02-12 21:54:03 +0000534 self.pending = []
mbligh5280e3b2008-12-22 14:39:28 +0000535
536
537 def add(self, result):
mbligh451ede12009-02-12 21:54:03 +0000538 if result.complete_count > result.pass_count:
539 self.fail.append(result)
540 elif result.incomplete_count > 0:
541 self.pending.append(result)
mbligh5280e3b2008-12-22 14:39:28 +0000542 else:
mbligh451ede12009-02-12 21:54:03 +0000543 self.good.append(result)
mbligh5280e3b2008-12-22 14:39:28 +0000544
545
546class RpcObject(object):
mbligh67647152008-11-19 00:18:14 +0000547 """
548 Generic object used to construct python objects from rpc calls
549 """
550 def __init__(self, afe, hash):
551 self.afe = afe
552 self.hash = hash
553 self.__dict__.update(hash)
554
555
556 def __str__(self):
557 return dump_object(self.__repr__(), self)
558
559
mbligh1354c9d2008-12-22 14:56:13 +0000560class ControlFile(RpcObject):
561 """
562 AFE control file object
563
564 Fields: synch_count, dependencies, control_file, is_server
565 """
566 def __repr__(self):
567 return 'CONTROL FILE: %s' % self.control_file
568
569
mbligh5280e3b2008-12-22 14:39:28 +0000570class Label(RpcObject):
mbligh67647152008-11-19 00:18:14 +0000571 """
572 AFE label object
573
574 Fields:
575 name, invalid, platform, kernel_config, id, only_if_needed
576 """
577 def __repr__(self):
578 return 'LABEL: %s' % self.name
579
580
581 def add_hosts(self, hosts):
582 return self.afe.run('label_add_hosts', self.id, hosts)
583
584
585 def remove_hosts(self, hosts):
586 return self.afe.run('label_remove_hosts', self.id, hosts)
587
588
mbligh5280e3b2008-12-22 14:39:28 +0000589class Acl(RpcObject):
mbligh67647152008-11-19 00:18:14 +0000590 """
591 AFE acl object
592
593 Fields:
594 users, hosts, description, name, id
595 """
596 def __repr__(self):
597 return 'ACL: %s' % self.name
598
599
600 def add_hosts(self, hosts):
601 self.afe.log('Adding hosts %s to ACL %s' % (hosts, self.name))
602 return self.afe.run('acl_group_add_hosts', self.id, hosts)
603
604
605 def remove_hosts(self, hosts):
606 self.afe.log('Removing hosts %s from ACL %s' % (hosts, self.name))
607 return self.afe.run('acl_group_remove_hosts', self.id, hosts)
608
609
mbligh54459c72009-01-21 19:26:44 +0000610 def add_users(self, users):
611 self.afe.log('Adding users %s to ACL %s' % (users, self.name))
612 return self.afe.run('acl_group_add_users', id=self.name, users=users)
613
614
mbligh5280e3b2008-12-22 14:39:28 +0000615class Job(RpcObject):
mbligh67647152008-11-19 00:18:14 +0000616 """
617 AFE job object
618
619 Fields:
620 name, control_file, control_type, synch_count, reboot_before,
621 run_verify, priority, email_list, created_on, dependencies,
622 timeout, owner, reboot_after, id
623 """
624 def __repr__(self):
625 return 'JOB: %s' % self.id
626
627
mbligh5280e3b2008-12-22 14:39:28 +0000628class JobStatus(RpcObject):
mbligh67647152008-11-19 00:18:14 +0000629 """
630 AFE job_status object
631
632 Fields:
633 status, complete, deleted, meta_host, host, active, execution_subdir, id
634 """
635 def __init__(self, afe, hash):
636 # This should call super
637 self.afe = afe
638 self.hash = hash
639 self.__dict__.update(hash)
mbligh5280e3b2008-12-22 14:39:28 +0000640 self.job = Job(afe, self.job)
mbligh67647152008-11-19 00:18:14 +0000641 if self.host:
mblighf9e35862009-02-26 01:03:11 +0000642 # get list of hosts from AFE; if a host is not present in autotest
643 # anymore, this returns an empty list.
644 afe_hosts = afe.get_hosts(hostname=self.host['hostname'])
645 if len(afe_hosts):
646 # host present, assign it!
647 self.host = afe_hosts[0]
648 else:
649 # AFE does not contain info anymore, set host to None
650 self.host = None
mbligh67647152008-11-19 00:18:14 +0000651
652
653 def __repr__(self):
mbligh451ede12009-02-12 21:54:03 +0000654 if self.host and self.host.hostname:
655 hostname = self.host.hostname
656 else:
657 hostname = 'None'
658 return 'JOB STATUS: %s-%s' % (self.job.id, hostname)
mbligh67647152008-11-19 00:18:14 +0000659
660
mbligh5280e3b2008-12-22 14:39:28 +0000661class Host(RpcObject):
mbligh67647152008-11-19 00:18:14 +0000662 """
663 AFE host object
664
665 Fields:
666 status, lock_time, locked_by, locked, hostname, invalid,
667 synch_id, labels, platform, protection, dirty, id
668 """
669 def __repr__(self):
670 return 'HOST OBJECT: %s' % self.hostname
671
672
673 def show(self):
674 labels = list(set(self.labels) - set([self.platform]))
675 print '%-6s %-7s %-7s %-16s %s' % (self.hostname, self.status,
676 self.locked, self.platform,
677 ', '.join(labels))
678
679
mbligh54459c72009-01-21 19:26:44 +0000680 def delete(self):
681 return self.afe.run('delete_host', id=self.id)
682
683
mbligh6463c4b2009-01-30 00:33:37 +0000684 def modify(self, **dargs):
685 return self.afe.run('modify_host', id=self.id, **dargs)
686
687
mbligh67647152008-11-19 00:18:14 +0000688 def get_acls(self):
689 return self.afe.get_acls(hosts__hostname=self.hostname)
690
691
692 def add_acl(self, acl_name):
693 self.afe.log('Adding ACL %s to host %s' % (acl_name, self.hostname))
694 return self.afe.run('acl_group_add_hosts', id=acl_name,
695 hosts=[self.hostname])
696
697
698 def remove_acl(self, acl_name):
699 self.afe.log('Removing ACL %s from host %s' % (acl_name, self.hostname))
700 return self.afe.run('acl_group_remove_hosts', id=acl_name,
701 hosts=[self.hostname])
702
703
704 def get_labels(self):
705 return self.afe.get_labels(host__hostname__in=[self.hostname])
706
707
708 def add_labels(self, labels):
709 self.afe.log('Adding labels %s to host %s' % (labels, self.hostname))
710 return self.afe.run('host_add_labels', id=self.id, labels=labels)
711
712
713 def remove_labels(self, labels):
714 self.afe.log('Removing labels %s from host %s' % (labels,self.hostname))
715 return self.afe.run('host_remove_labels', id=self.id, labels=labels)
mbligh5b618382008-12-03 15:24:01 +0000716
717
mbligh54459c72009-01-21 19:26:44 +0000718class User(RpcObject):
719 def __repr__(self):
720 return 'USER: %s' % self.login
721
722
mbligh5280e3b2008-12-22 14:39:28 +0000723class TestStatus(RpcObject):
mblighc31e4022008-12-11 19:32:30 +0000724 """
725 TKO test status object
726
727 Fields:
728 test_idx, hostname, testname, id
729 complete_count, incomplete_count, group_count, pass_count
730 """
731 def __repr__(self):
732 return 'TEST STATUS: %s' % self.id
733
734
mbligh5b618382008-12-03 15:24:01 +0000735class MachineTestPairing(object):
736 """
737 Object representing the pairing of a machine label with a control file
mbligh1f23f362008-12-22 14:46:12 +0000738
739 machine_label: use machines from this label
740 control_file: use this control file (by name in the frontend)
741 platforms: list of rexeps to filter platforms by. [] => no filtering
mbligh5b618382008-12-03 15:24:01 +0000742 """
mbligh1354c9d2008-12-22 14:56:13 +0000743 def __init__(self, machine_label, control_file, platforms=[],
mblighb1ce3b32009-02-17 16:00:50 +0000744 container=False, synch_job=False):
mbligh5b618382008-12-03 15:24:01 +0000745 self.machine_label = machine_label
746 self.control_file = control_file
mbligh1f23f362008-12-22 14:46:12 +0000747 self.platforms = platforms
mbligh1354c9d2008-12-22 14:56:13 +0000748 self.container = container
mbligh2a894102009-02-26 00:48:58 +0000749 self.synch_job = synch_job
mbligh1354c9d2008-12-22 14:56:13 +0000750
751
752 def __repr__(self):
753 return '%s %s %s %s' % (self.machine_label, self.control_file,
754 self.platforms, self.container)