blob: 57bde3483ebe7912ae8818b403effb7bc6088a8c [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
22
mbligh37eceaa2008-12-15 22:56:37 +000023GLOBAL_CONFIG = global_config.global_config
24DEFAULT_SERVER = 'autotest'
25
mbligh67647152008-11-19 00:18:14 +000026
27def dump_object(header, obj):
28 """
29 Standard way to print out the frontend objects (eg job, host, acl, label)
30 in a human-readable fashion for debugging
31 """
32 result = header + '\n'
33 for key in obj.hash:
34 if key == 'afe' or key == 'hash':
35 continue
36 result += '%20s: %s\n' % (key, obj.hash[key])
37 return result
38
39
mbligh5280e3b2008-12-22 14:39:28 +000040class RpcClient(object):
mbligh67647152008-11-19 00:18:14 +000041 """
42 AFE class for communicating with the autotest frontend
43
44 All the constructors go in the afe class.
45 Manipulating methods go in the classes themselves
46 """
mblighc31e4022008-12-11 19:32:30 +000047 def __init__(self, path, user, web_server, print_log, debug):
mbligh67647152008-11-19 00:18:14 +000048 """
49 Create a cached instance of a connection to the AFE
50
51 user: username to connect as
52 web_server: AFE instance to connect to
53 print_log: pring a logging message to stdout on every operation
54 debug: print out all RPC traffic
55 """
mblighc31e4022008-12-11 19:32:30 +000056 if not user:
57 user = os.environ.get('LOGNAME')
58 if not web_server:
mbligh37eceaa2008-12-15 22:56:37 +000059 web_server = 'http://' + GLOBAL_CONFIG.get_config_value(
60 'SERVER', 'hostname', default=DEFAULT_SERVER)
mbligh67647152008-11-19 00:18:14 +000061 self.user = user
62 self.print_log = print_log
63 self.debug = debug
64 headers = {'AUTHORIZATION' : self.user}
mblighc31e4022008-12-11 19:32:30 +000065 rpc_server = web_server + path
mbligh67647152008-11-19 00:18:14 +000066 self.proxy = rpc_client_lib.get_proxy(rpc_server, headers=headers)
67
68
69 def run(self, call, **dargs):
70 """
71 Make a RPC call to the AFE server
72 """
73 rpc_call = getattr(self.proxy, call)
74 if self.debug:
75 print 'DEBUG: %s %s' % (call, dargs)
76 return utils.strip_unicode(rpc_call(**dargs))
77
78
79 def log(self, message):
80 if self.print_log:
81 print message
82
83
mbligh5280e3b2008-12-22 14:39:28 +000084class TKO(RpcClient):
mblighc31e4022008-12-11 19:32:30 +000085 def __init__(self, user=None, web_server=None, print_log=True, debug=False):
mbligh5280e3b2008-12-22 14:39:28 +000086 super(TKO, self).__init__('/new_tko/server/noauth/rpc/', user,
mblighc31e4022008-12-11 19:32:30 +000087 web_server, print_log, debug)
88
89
90 def get_status_counts(self, job, **data):
91 entries = self.run('get_status_counts',
92 group_by=['hostname', 'test_name'],
93 job_tag__startswith='%s-' % job, **data)
mbligh5280e3b2008-12-22 14:39:28 +000094 return [TestStatus(self, e) for e in entries['groups']]
mblighc31e4022008-12-11 19:32:30 +000095
96
mbligh5280e3b2008-12-22 14:39:28 +000097class AFE(RpcClient):
mblighc31e4022008-12-11 19:32:30 +000098 def __init__(self, user=None, web_server=None, print_log=True, debug=False):
mbligh5280e3b2008-12-22 14:39:28 +000099 super(AFE, self).__init__('/afe/server/noauth/rpc/', user, web_server,
mblighc31e4022008-12-11 19:32:30 +0000100 print_log, debug)
101
102
mbligh67647152008-11-19 00:18:14 +0000103 def host_statuses(self, live=None):
104 dead_statuses = ['Dead', 'Repair Failed']
105 statuses = self.run('get_static_data')['host_statuses']
106 if live == True:
107 return list(set(statuses) - set(['Dead', 'Repair Failed']))
108 if live == False:
109 return dead_statuses
110 else:
111 return statuses
112
113
114 def get_hosts(self, **dargs):
115 hosts = self.run('get_hosts', **dargs)
mbligh5280e3b2008-12-22 14:39:28 +0000116 return [Host(self, h) for h in hosts]
mbligh67647152008-11-19 00:18:14 +0000117
118
119 def create_host(self, hostname, **dargs):
120 id = self.run('add_host', **dargs)
121 return self.get_hosts(id=id)[0]
122
123
124 def get_labels(self, **dargs):
125 labels = self.run('get_labels', **dargs)
mbligh5280e3b2008-12-22 14:39:28 +0000126 return [Label(self, l) for l in labels]
mbligh67647152008-11-19 00:18:14 +0000127
128
129 def create_label(self, name, **dargs):
130 id = self.run('add_label', **dargs)
131 return self.get_labels(id=id)[0]
132
133
134 def get_acls(self, **dargs):
135 acls = self.run('get_acl_groups', **dargs)
mbligh5280e3b2008-12-22 14:39:28 +0000136 return [Acl(self, a) for a in acls]
mbligh67647152008-11-19 00:18:14 +0000137
138
139 def create_acl(self, name, **dargs):
140 id = self.run('add_acl_group', **dargs)
141 return self.get_acls(id=id)[0]
142
143
144 def get_jobs(self, summary=False, **dargs):
145 if summary:
146 jobs_data = self.run('get_jobs_summary', **dargs)
147 else:
148 jobs_data = self.run('get_jobs', **dargs)
mbligh5280e3b2008-12-22 14:39:28 +0000149 return [Job(self, j) for j in jobs_data]
mbligh67647152008-11-19 00:18:14 +0000150
151
152 def get_host_queue_entries(self, **data):
153 entries = self.run('get_host_queue_entries', **data)
mbligh5280e3b2008-12-22 14:39:28 +0000154 return [JobStatus(self, e) for e in entries]
mbligh67647152008-11-19 00:18:14 +0000155
156
157 def create_job_by_test(self, tests, kernel=None, **dargs):
158 """
159 Given a test name, fetch the appropriate control file from the server
160 and submit it
161 """
162 results = self.run('generate_control_file', tests=tests, kernel=kernel,
163 use_container=False, do_push_packages=True)
164 if results['is_server']:
165 dargs['control_type'] = 'Server'
166 else:
167 dargs['control_type'] = 'Client'
168 dargs['dependencies'] = dargs.get('dependencies', []) + \
169 results['dependencies']
170 dargs['control_file'] = results['control_file']
171 dargs['synch_count'] = results['synch_count']
172 return self.create_job(**dargs)
173
174
175 def create_job(self, control_file, name=' ', priority='Medium',
176 control_type='Client', **dargs):
177 id = self.run('create_job', name=name, priority=priority,
178 control_file=control_file, control_type=control_type, **dargs)
179 return self.get_jobs(id=id)[0]
180
181
mbligh1f23f362008-12-22 14:46:12 +0000182 def run_test_suites(self, pairings, kernel, kernel_label, priority='Medium',
183 wait=True, poll_interval=5, email_from=None,
184 email_to=None):
mbligh5b618382008-12-03 15:24:01 +0000185 """
186 Run a list of test suites on a particular kernel.
187
188 Poll for them to complete, and return whether they worked or not.
189
190 pairings: list of MachineTestPairing objects to invoke
191 kernel: name of the kernel to run
192 kernel_label: label of the kernel to run
193 (<kernel-version> : <config> : <date>)
194 wait: boolean - wait for the results to come back?
195 poll_interval: interval between polling for job results (in minutes)
mbligh45ffc432008-12-09 23:35:17 +0000196 email_from: send notification email upon completion from here
197 email_from: send notification email upon completion to here
mbligh5b618382008-12-03 15:24:01 +0000198 """
199 jobs = []
200 for pairing in pairings:
mbligh1f23f362008-12-22 14:46:12 +0000201 job = self.invoke_test(pairing, kernel, kernel_label, priority)
mbligh45ffc432008-12-09 23:35:17 +0000202 job.notified = False
203 jobs.append(job)
mbligh5280e3b2008-12-22 14:39:28 +0000204 # disabled - this is just for debugging: mbligh
205 # if email_from and email_to:
206 # subject = 'Testing started: %s : %s' % (job.name, job.id)
207 # utils.send_email(email_from, email_to, subject, subject)
mbligh5b618382008-12-03 15:24:01 +0000208 if not wait:
209 return
mbligh5280e3b2008-12-22 14:39:28 +0000210 tko = TKO()
mbligh5b618382008-12-03 15:24:01 +0000211 while True:
212 time.sleep(60 * poll_interval)
mbligh5280e3b2008-12-22 14:39:28 +0000213 result = self.poll_all_jobs(tko, jobs, email_from, email_to)
mbligh5b618382008-12-03 15:24:01 +0000214 if result is not None:
215 return result
216
217
mbligh45ffc432008-12-09 23:35:17 +0000218 def result_notify(self, job, email_from, email_to):
mbligh5b618382008-12-03 15:24:01 +0000219 """
mbligh45ffc432008-12-09 23:35:17 +0000220 Notify about the result of a job. Will always print, if email data
221 is provided, will send email for it as well.
222
223 job: job object to notify about
224 email_from: send notification email upon completion from here
225 email_from: send notification email upon completion to here
226 """
227 if job.result == True:
228 subject = 'Testing PASSED: '
229 else:
230 subject = 'Testing FAILED: '
231 subject += '%s : %s\n' % (job.name, job.id)
232 text = []
233 for platform in job.results_platform_map:
234 for status in job.results_platform_map[platform]:
235 if status == 'Total':
236 continue
237 hosts = ','.join(job.results_platform_map[platform][status])
mbligh37eceaa2008-12-15 22:56:37 +0000238 text.append('%20s %10s %s\n' % (platform, status, hosts))
239
240 tko_base_url = 'http://%s/tko' % GLOBAL_CONFIG.get_config_value(
241 'SERVER', 'hostname', default=DEFAULT_SERVER)
242
243 params = ('columns=test',
244 'rows=machine_group',
245 "condition=tag~'%s-%%25'" % job.id,
246 'title=Report')
247 query_string = '&'.join(params)
248 url = '%s/compose_query.cgi?%s' % (tko_base_url, query_string)
249 text.append('\n')
250 text.append(url)
251
252 body = '\n'.join(text)
253 print '---------------------------------------------------'
254 print 'Subject: ', subject
mbligh45ffc432008-12-09 23:35:17 +0000255 print body
mbligh37eceaa2008-12-15 22:56:37 +0000256 print '---------------------------------------------------'
mbligh45ffc432008-12-09 23:35:17 +0000257 if email_from and email_to:
mbligh37eceaa2008-12-15 22:56:37 +0000258 print 'Sending email ...'
mbligh45ffc432008-12-09 23:35:17 +0000259 utils.send_email(email_from, email_to, subject, body)
260 print
mbligh37eceaa2008-12-15 22:56:37 +0000261
mbligh45ffc432008-12-09 23:35:17 +0000262
mbligh5280e3b2008-12-22 14:39:28 +0000263 def poll_all_jobs(self, tko, jobs, email_from, email_to):
mbligh45ffc432008-12-09 23:35:17 +0000264 """
265 Poll all jobs in a list.
266 jobs: list of job objects to poll
267 email_from: send notification email upon completion from here
268 email_from: send notification email upon completion to here
269
270 Returns:
mbligh5b618382008-12-03 15:24:01 +0000271 a) All complete successfully (return True)
272 b) One or more has failed (return False)
273 c) Cannot tell yet (return None)
274 """
mbligh45ffc432008-12-09 23:35:17 +0000275 results = []
mbligh5b618382008-12-03 15:24:01 +0000276 for job in jobs:
mbligh5280e3b2008-12-22 14:39:28 +0000277 job.result = self.poll_job_results(tko, job, debug=False)
mbligh45ffc432008-12-09 23:35:17 +0000278 results.append(job.result)
279 if job.result is not None and not job.notified:
280 self.result_notify(job, email_from, email_to)
281 job.notified = True
282
283 if job.result is None:
284 print 'PENDING',
285 elif job.result == True:
286 print 'PASSED',
287 elif job.result == False:
288 print 'FAILED',
289 print ' %s : %s' % (job.id, job.name)
290
291 if None in results:
292 return None
293 elif False in results:
294 return False
295 else:
296 return True
mbligh5b618382008-12-03 15:24:01 +0000297
298
mbligh1f23f362008-12-22 14:46:12 +0000299 def _included_platform(self, host, platforms):
300 """
301 See if host's platforms matches any of the patterns in the included
302 platforms list.
303 """
304 if not platforms:
305 return True # No filtering of platforms
306 for platform in platforms:
307 if re.search(platform, host.platform):
308 return True
309 return False
310
311
mbligh5b618382008-12-03 15:24:01 +0000312 def invoke_test(self, pairing, kernel, kernel_label, priority='Medium'):
313 """
314 Given a pairing of a control file to a machine label, find all machines
315 with that label, and submit that control file to them.
316
317 Returns a job object
318 """
319 job_name = '%s : %s' % (pairing.machine_label, kernel_label)
320 hosts = self.get_hosts(multiple_labels=[pairing.machine_label])
mbligh1f23f362008-12-22 14:46:12 +0000321 platforms = pairing.platforms
322 hosts = [h for h in hosts if self._included_platform(h, platforms)]
mbligh45ffc432008-12-09 23:35:17 +0000323 host_list = [h.hostname for h in hosts if h.status != 'Repair Failed']
mbligh1f23f362008-12-22 14:46:12 +0000324 print 'HOSTS: %s' % host_list
mbligh5b618382008-12-03 15:24:01 +0000325 new_job = self.create_job_by_test(name=job_name,
326 dependencies=[pairing.machine_label],
327 tests=[pairing.control_file],
328 priority=priority,
329 hosts=host_list,
330 kernel=kernel)
331 print 'Invoked test %s : %s' % (new_job.id, job_name)
332 return new_job
333
334
mbligh5280e3b2008-12-22 14:39:28 +0000335 def _job_test_results(self, tko, job):
mbligh5b618382008-12-03 15:24:01 +0000336 """
mbligh5280e3b2008-12-22 14:39:28 +0000337 Retrieve test results for a job
mbligh5b618382008-12-03 15:24:01 +0000338 """
mbligh5280e3b2008-12-22 14:39:28 +0000339 job.test_status = {}
340 try:
341 test_statuses = tko.get_status_counts(job=job.id)
342 except Exception:
343 print "Ignoring exception on poll job; RPC interface is flaky"
344 traceback.print_exc()
345 return
346
347 for test_status in test_statuses:
348 hostname = test_status.hostname
349 if hostname not in job.test_status:
350 job.test_status[hostname] = TestResults()
351 job.test_status[hostname].add(test_status)
352
353
354 def _job_results_platform_map(self, job):
355 job.results_platform_map = {}
mbligh5b618382008-12-03 15:24:01 +0000356 try:
mbligh45ffc432008-12-09 23:35:17 +0000357 job_statuses = self.get_host_queue_entries(job=job.id)
mbligh5b618382008-12-03 15:24:01 +0000358 except Exception:
359 print "Ignoring exception on poll job; RPC interface is flaky"
360 traceback.print_exc()
361 return None
mbligh5280e3b2008-12-22 14:39:28 +0000362
mbligh5b618382008-12-03 15:24:01 +0000363 platform_map = {}
mbligh5280e3b2008-12-22 14:39:28 +0000364 job.job_status = {}
mbligh5b618382008-12-03 15:24:01 +0000365 for job_status in job_statuses:
366 hostname = job_status.host.hostname
mbligh5280e3b2008-12-22 14:39:28 +0000367 job.job_status[hostname] = job_status.status
mbligh5b618382008-12-03 15:24:01 +0000368 status = job_status.status
mbligh5280e3b2008-12-22 14:39:28 +0000369 if hostname in job.test_status and job.test_status[hostname].fail:
370 # Job status doesn't reflect failed tests, override that
371 status = 'Failed'
mbligh5b618382008-12-03 15:24:01 +0000372 platform = job_status.host.platform
373 if platform not in platform_map:
374 platform_map[platform] = {'Total' : [hostname]}
375 else:
376 platform_map[platform]['Total'].append(hostname)
377 new_host_list = platform_map[platform].get(status, []) + [hostname]
378 platform_map[platform][status] = new_host_list
mbligh45ffc432008-12-09 23:35:17 +0000379 job.results_platform_map = platform_map
mbligh5280e3b2008-12-22 14:39:28 +0000380
381
382 def poll_job_results(self, tko, job, debug=False):
383 """
384 Analyse all job results by platform, return:
mbligh5b618382008-12-03 15:24:01 +0000385
mbligh5280e3b2008-12-22 14:39:28 +0000386 False: if any platform has more than one failure
387 None: if any platform has more than one machine not yet Good.
388 True: if all platforms have at least all-but-one machines Good.
389 """
390 self._job_test_results(tko, job)
391 self._job_results_platform_map(job)
392
mbligh5b618382008-12-03 15:24:01 +0000393 good_platforms = []
394 bad_platforms = []
395 unknown_platforms = []
mbligh5280e3b2008-12-22 14:39:28 +0000396 platform_map = job.results_platform_map
mbligh5b618382008-12-03 15:24:01 +0000397 for platform in platform_map:
398 total = len(platform_map[platform]['Total'])
399 completed = len(platform_map[platform].get('Completed', []))
400 failed = len(platform_map[platform].get('Failed', []))
401 if failed > 1:
402 bad_platforms.append(platform)
403 elif completed + 1 >= total:
404 # if all or all but one are good, call the job good.
405 good_platforms.append(platform)
406 else:
407 unknown_platforms.append(platform)
408 detail = []
409 for status in platform_map[platform]:
410 if status == 'Total':
411 continue
412 detail.append('%s=%s' % (status,platform_map[platform][status]))
413 if debug:
414 print '%20s %d/%d %s' % (platform, completed, total,
415 ' '.join(detail))
416 print
417
418 if len(bad_platforms) > 0:
419 if debug:
420 print 'Result bad - platforms: ' + ' '.join(bad_platforms)
421 return False
422 if len(unknown_platforms) > 0:
423 if debug:
424 platform_list = ' '.join(unknown_platforms)
425 print 'Result unknown - platforms: ', platform_list
426 return None
427 if debug:
428 platform_list = ' '.join(good_platforms)
429 print 'Result good - all platforms passed: ', platform_list
430 return True
431
432
mbligh5280e3b2008-12-22 14:39:28 +0000433class TestResults(object):
434 """
435 Container class used to hold the results of the tests for a job
436 """
437 def __init__(self):
438 self.good = []
439 self.fail = []
440
441
442 def add(self, result):
443 if result.complete_count - result.pass_count > 0:
444 self.fail.append(result.test_name)
445 else:
446 self.good.append(result.test_name)
447
448
449class RpcObject(object):
mbligh67647152008-11-19 00:18:14 +0000450 """
451 Generic object used to construct python objects from rpc calls
452 """
453 def __init__(self, afe, hash):
454 self.afe = afe
455 self.hash = hash
456 self.__dict__.update(hash)
457
458
459 def __str__(self):
460 return dump_object(self.__repr__(), self)
461
462
mbligh5280e3b2008-12-22 14:39:28 +0000463class Label(RpcObject):
mbligh67647152008-11-19 00:18:14 +0000464 """
465 AFE label object
466
467 Fields:
468 name, invalid, platform, kernel_config, id, only_if_needed
469 """
470 def __repr__(self):
471 return 'LABEL: %s' % self.name
472
473
474 def add_hosts(self, hosts):
475 return self.afe.run('label_add_hosts', self.id, hosts)
476
477
478 def remove_hosts(self, hosts):
479 return self.afe.run('label_remove_hosts', self.id, hosts)
480
481
mbligh5280e3b2008-12-22 14:39:28 +0000482class Acl(RpcObject):
mbligh67647152008-11-19 00:18:14 +0000483 """
484 AFE acl object
485
486 Fields:
487 users, hosts, description, name, id
488 """
489 def __repr__(self):
490 return 'ACL: %s' % self.name
491
492
493 def add_hosts(self, hosts):
494 self.afe.log('Adding hosts %s to ACL %s' % (hosts, self.name))
495 return self.afe.run('acl_group_add_hosts', self.id, hosts)
496
497
498 def remove_hosts(self, hosts):
499 self.afe.log('Removing hosts %s from ACL %s' % (hosts, self.name))
500 return self.afe.run('acl_group_remove_hosts', self.id, hosts)
501
502
mbligh5280e3b2008-12-22 14:39:28 +0000503class Job(RpcObject):
mbligh67647152008-11-19 00:18:14 +0000504 """
505 AFE job object
506
507 Fields:
508 name, control_file, control_type, synch_count, reboot_before,
509 run_verify, priority, email_list, created_on, dependencies,
510 timeout, owner, reboot_after, id
511 """
512 def __repr__(self):
513 return 'JOB: %s' % self.id
514
515
mbligh5280e3b2008-12-22 14:39:28 +0000516class JobStatus(RpcObject):
mbligh67647152008-11-19 00:18:14 +0000517 """
518 AFE job_status object
519
520 Fields:
521 status, complete, deleted, meta_host, host, active, execution_subdir, id
522 """
523 def __init__(self, afe, hash):
524 # This should call super
525 self.afe = afe
526 self.hash = hash
527 self.__dict__.update(hash)
mbligh5280e3b2008-12-22 14:39:28 +0000528 self.job = Job(afe, self.job)
mbligh67647152008-11-19 00:18:14 +0000529 if self.host:
530 self.host = afe.get_hosts(hostname=self.host['hostname'])[0]
531
532
533 def __repr__(self):
534 return 'JOB STATUS: %s-%s' % (self.job.id, self.host.hostname)
535
536
mbligh5280e3b2008-12-22 14:39:28 +0000537class Host(RpcObject):
mbligh67647152008-11-19 00:18:14 +0000538 """
539 AFE host object
540
541 Fields:
542 status, lock_time, locked_by, locked, hostname, invalid,
543 synch_id, labels, platform, protection, dirty, id
544 """
545 def __repr__(self):
546 return 'HOST OBJECT: %s' % self.hostname
547
548
549 def show(self):
550 labels = list(set(self.labels) - set([self.platform]))
551 print '%-6s %-7s %-7s %-16s %s' % (self.hostname, self.status,
552 self.locked, self.platform,
553 ', '.join(labels))
554
555
556 def get_acls(self):
557 return self.afe.get_acls(hosts__hostname=self.hostname)
558
559
560 def add_acl(self, acl_name):
561 self.afe.log('Adding ACL %s to host %s' % (acl_name, self.hostname))
562 return self.afe.run('acl_group_add_hosts', id=acl_name,
563 hosts=[self.hostname])
564
565
566 def remove_acl(self, acl_name):
567 self.afe.log('Removing ACL %s from host %s' % (acl_name, self.hostname))
568 return self.afe.run('acl_group_remove_hosts', id=acl_name,
569 hosts=[self.hostname])
570
571
572 def get_labels(self):
573 return self.afe.get_labels(host__hostname__in=[self.hostname])
574
575
576 def add_labels(self, labels):
577 self.afe.log('Adding labels %s to host %s' % (labels, self.hostname))
578 return self.afe.run('host_add_labels', id=self.id, labels=labels)
579
580
581 def remove_labels(self, labels):
582 self.afe.log('Removing labels %s from host %s' % (labels,self.hostname))
583 return self.afe.run('host_remove_labels', id=self.id, labels=labels)
mbligh5b618382008-12-03 15:24:01 +0000584
585
mbligh5280e3b2008-12-22 14:39:28 +0000586class TestStatus(RpcObject):
mblighc31e4022008-12-11 19:32:30 +0000587 """
588 TKO test status object
589
590 Fields:
591 test_idx, hostname, testname, id
592 complete_count, incomplete_count, group_count, pass_count
593 """
594 def __repr__(self):
595 return 'TEST STATUS: %s' % self.id
596
597
mbligh5b618382008-12-03 15:24:01 +0000598class MachineTestPairing(object):
599 """
600 Object representing the pairing of a machine label with a control file
mbligh1f23f362008-12-22 14:46:12 +0000601
602 machine_label: use machines from this label
603 control_file: use this control file (by name in the frontend)
604 platforms: list of rexeps to filter platforms by. [] => no filtering
mbligh5b618382008-12-03 15:24:01 +0000605 """
mbligh1f23f362008-12-22 14:46:12 +0000606 def __init__(self, machine_label, control_file, platforms=[]):
mbligh5b618382008-12-03 15:24:01 +0000607 self.machine_label = machine_label
608 self.control_file = control_file
mbligh1f23f362008-12-22 14:46:12 +0000609 self.platforms = platforms