blob: 809f10ba513387cfc83bbd08d9e347a28938fc6e [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:
Aviv Keshet2c709f62013-05-07 12:52:15 -070012 http://www.chromium.org/chromium-os/testing/afe-rpc-infrastructure
mblighc31e4022008-12-11 19:32:30 +000013 http://docs.djangoproject.com/en/dev/ref/models/querysets/#queryset-api
mbligh67647152008-11-19 00:18:14 +000014"""
15
Dan Shie8e0c052015-09-01 00:27:27 -070016import getpass
17import os
18import re
19import time
20import traceback
21
mbligh67647152008-11-19 00:18:14 +000022import common
23from autotest_lib.frontend.afe import rpc_client_lib
Dan Shie8e0c052015-09-01 00:27:27 -070024from autotest_lib.client.common_lib import control_data
mbligh37eceaa2008-12-15 22:56:37 +000025from autotest_lib.client.common_lib import global_config
mbligh67647152008-11-19 00:18:14 +000026from autotest_lib.client.common_lib import utils
Dan Shie8e0c052015-09-01 00:27:27 -070027from autotest_lib.client.common_lib.cros.graphite import autotest_stats
Scott Zawalski63470dd2012-09-05 00:49:43 -040028from autotest_lib.tko import db
29
30
mbligh4e576612008-12-22 14:56:36 +000031try:
32 from autotest_lib.server.site_common import site_utils as server_utils
33except:
34 from autotest_lib.server import utils as server_utils
35form_ntuples_from_machines = server_utils.form_ntuples_from_machines
mbligh67647152008-11-19 00:18:14 +000036
mbligh37eceaa2008-12-15 22:56:37 +000037GLOBAL_CONFIG = global_config.global_config
38DEFAULT_SERVER = 'autotest'
39
Dan Shie8e0c052015-09-01 00:27:27 -070040_tko_timer = autotest_stats.Timer('tko')
41
mbligh67647152008-11-19 00:18:14 +000042def dump_object(header, obj):
43 """
44 Standard way to print out the frontend objects (eg job, host, acl, label)
45 in a human-readable fashion for debugging
46 """
47 result = header + '\n'
48 for key in obj.hash:
49 if key == 'afe' or key == 'hash':
50 continue
51 result += '%20s: %s\n' % (key, obj.hash[key])
52 return result
53
54
mbligh5280e3b2008-12-22 14:39:28 +000055class RpcClient(object):
mbligh67647152008-11-19 00:18:14 +000056 """
mbligh451ede12009-02-12 21:54:03 +000057 Abstract RPC class for communicating with the autotest frontend
58 Inherited for both TKO and AFE uses.
mbligh67647152008-11-19 00:18:14 +000059
mbligh1ef218d2009-08-03 16:57:56 +000060 All the constructors go in the afe / tko class.
mbligh451ede12009-02-12 21:54:03 +000061 Manipulating methods go in the object classes themselves
mbligh67647152008-11-19 00:18:14 +000062 """
mbligh99b24f42009-06-08 16:45:55 +000063 def __init__(self, path, user, server, print_log, debug, reply_debug):
mbligh67647152008-11-19 00:18:14 +000064 """
mbligh451ede12009-02-12 21:54:03 +000065 Create a cached instance of a connection to the frontend
mbligh67647152008-11-19 00:18:14 +000066
67 user: username to connect as
mbligh451ede12009-02-12 21:54:03 +000068 server: frontend server to connect to
mbligh67647152008-11-19 00:18:14 +000069 print_log: pring a logging message to stdout on every operation
70 debug: print out all RPC traffic
71 """
Dan Shiff78f112015-06-12 13:34:02 -070072 if not user and utils.is_in_container():
73 user = GLOBAL_CONFIG.get_config_value('SSP', 'user', default=None)
mblighc31e4022008-12-11 19:32:30 +000074 if not user:
mblighdb59e3c2009-11-21 01:45:18 +000075 user = getpass.getuser()
mbligh451ede12009-02-12 21:54:03 +000076 if not server:
mbligh475f7762009-01-30 00:34:04 +000077 if 'AUTOTEST_WEB' in os.environ:
mbligh451ede12009-02-12 21:54:03 +000078 server = os.environ['AUTOTEST_WEB']
mbligh475f7762009-01-30 00:34:04 +000079 else:
mbligh451ede12009-02-12 21:54:03 +000080 server = GLOBAL_CONFIG.get_config_value('SERVER', 'hostname',
81 default=DEFAULT_SERVER)
82 self.server = server
mbligh67647152008-11-19 00:18:14 +000083 self.user = user
84 self.print_log = print_log
85 self.debug = debug
mbligh99b24f42009-06-08 16:45:55 +000086 self.reply_debug = reply_debug
Scott Zawalski347aaf42012-04-03 16:33:00 -040087 headers = {'AUTHORIZATION': self.user}
88 rpc_server = 'http://' + server + path
mbligh1354c9d2008-12-22 14:56:13 +000089 if debug:
90 print 'SERVER: %s' % rpc_server
91 print 'HEADERS: %s' % headers
mbligh67647152008-11-19 00:18:14 +000092 self.proxy = rpc_client_lib.get_proxy(rpc_server, headers=headers)
93
94
95 def run(self, call, **dargs):
96 """
97 Make a RPC call to the AFE server
98 """
99 rpc_call = getattr(self.proxy, call)
100 if self.debug:
101 print 'DEBUG: %s %s' % (call, dargs)
mbligh451ede12009-02-12 21:54:03 +0000102 try:
mbligh99b24f42009-06-08 16:45:55 +0000103 result = utils.strip_unicode(rpc_call(**dargs))
104 if self.reply_debug:
105 print result
106 return result
mbligh451ede12009-02-12 21:54:03 +0000107 except Exception:
mbligh451ede12009-02-12 21:54:03 +0000108 raise
mbligh67647152008-11-19 00:18:14 +0000109
110
111 def log(self, message):
112 if self.print_log:
113 print message
114
115
jamesrenc3940222010-02-19 21:57:37 +0000116class Planner(RpcClient):
117 def __init__(self, user=None, server=None, print_log=True, debug=False,
118 reply_debug=False):
119 super(Planner, self).__init__(path='/planner/server/rpc/',
120 user=user,
121 server=server,
122 print_log=print_log,
123 debug=debug,
124 reply_debug=reply_debug)
125
126
mbligh5280e3b2008-12-22 14:39:28 +0000127class TKO(RpcClient):
mbligh99b24f42009-06-08 16:45:55 +0000128 def __init__(self, user=None, server=None, print_log=True, debug=False,
129 reply_debug=False):
Scott Zawalski347aaf42012-04-03 16:33:00 -0400130 super(TKO, self).__init__(path='/new_tko/server/noauth/rpc/',
mbligh99b24f42009-06-08 16:45:55 +0000131 user=user,
132 server=server,
133 print_log=print_log,
134 debug=debug,
135 reply_debug=reply_debug)
Scott Zawalski63470dd2012-09-05 00:49:43 -0400136 self._db = None
137
138
Dan Shie8e0c052015-09-01 00:27:27 -0700139 @_tko_timer.decorate
Scott Zawalski63470dd2012-09-05 00:49:43 -0400140 def get_job_test_statuses_from_db(self, job_id):
141 """Get job test statuses from the database.
142
143 Retrieve a set of fields from a job that reflect the status of each test
144 run within a job.
145 fields retrieved: status, test_name, reason, test_started_time,
146 test_finished_time, afe_job_id, job_owner, hostname.
147
148 @param job_id: The afe job id to look up.
149 @returns a TestStatus object of the resulting information.
150 """
151 if self._db is None:
Dan Shie8e0c052015-09-01 00:27:27 -0700152 self._db = db.db()
Fang Deng5c508332014-03-19 10:26:00 -0700153 fields = ['status', 'test_name', 'subdir', 'reason',
154 'test_started_time', 'test_finished_time', 'afe_job_id',
155 'job_owner', 'hostname', 'job_tag']
Scott Zawalski63470dd2012-09-05 00:49:43 -0400156 table = 'tko_test_view_2'
157 where = 'job_tag like "%s-%%"' % job_id
158 test_status = []
159 # Run commit before we query to ensure that we are pulling the latest
160 # results.
161 self._db.commit()
162 for entry in self._db.select(','.join(fields), table, (where, None)):
163 status_dict = {}
164 for key,value in zip(fields, entry):
165 # All callers expect values to be a str object.
166 status_dict[key] = str(value)
167 # id is used by TestStatus to uniquely identify each Test Status
168 # obj.
169 status_dict['id'] = [status_dict['reason'], status_dict['hostname'],
170 status_dict['test_name']]
171 test_status.append(status_dict)
172
173 return [TestStatus(self, e) for e in test_status]
mblighc31e4022008-12-11 19:32:30 +0000174
175
176 def get_status_counts(self, job, **data):
177 entries = self.run('get_status_counts',
mbligh1ef218d2009-08-03 16:57:56 +0000178 group_by=['hostname', 'test_name', 'reason'],
mblighc31e4022008-12-11 19:32:30 +0000179 job_tag__startswith='%s-' % job, **data)
mbligh5280e3b2008-12-22 14:39:28 +0000180 return [TestStatus(self, e) for e in entries['groups']]
mblighc31e4022008-12-11 19:32:30 +0000181
182
mbligh5280e3b2008-12-22 14:39:28 +0000183class AFE(RpcClient):
mbligh17c75e62009-06-08 16:18:21 +0000184 def __init__(self, user=None, server=None, print_log=True, debug=False,
mbligh99b24f42009-06-08 16:45:55 +0000185 reply_debug=False, job=None):
mbligh17c75e62009-06-08 16:18:21 +0000186 self.job = job
Scott Zawalski347aaf42012-04-03 16:33:00 -0400187 super(AFE, self).__init__(path='/afe/server/noauth/rpc/',
mbligh99b24f42009-06-08 16:45:55 +0000188 user=user,
189 server=server,
190 print_log=print_log,
191 debug=debug,
192 reply_debug=reply_debug)
mblighc31e4022008-12-11 19:32:30 +0000193
mbligh1ef218d2009-08-03 16:57:56 +0000194
mbligh67647152008-11-19 00:18:14 +0000195 def host_statuses(self, live=None):
jamesren121eee62010-04-13 19:10:12 +0000196 dead_statuses = ['Repair Failed', 'Repairing']
mbligh67647152008-11-19 00:18:14 +0000197 statuses = self.run('get_static_data')['host_statuses']
198 if live == True:
mblighc2847b72009-03-25 19:32:20 +0000199 return list(set(statuses) - set(dead_statuses))
mbligh67647152008-11-19 00:18:14 +0000200 if live == False:
201 return dead_statuses
202 else:
203 return statuses
204
205
mbligh71094012009-12-19 05:35:21 +0000206 @staticmethod
207 def _dict_for_host_query(hostnames=(), status=None, label=None):
208 query_args = {}
mbligh4e545a52009-12-19 05:30:39 +0000209 if hostnames:
210 query_args['hostname__in'] = hostnames
211 if status:
212 query_args['status'] = status
213 if label:
214 query_args['labels__name'] = label
mbligh71094012009-12-19 05:35:21 +0000215 return query_args
216
217
218 def get_hosts(self, hostnames=(), status=None, label=None, **dargs):
219 query_args = dict(dargs)
220 query_args.update(self._dict_for_host_query(hostnames=hostnames,
221 status=status,
222 label=label))
223 hosts = self.run('get_hosts', **query_args)
224 return [Host(self, h) for h in hosts]
225
226
227 def get_hostnames(self, status=None, label=None, **dargs):
228 """Like get_hosts() but returns hostnames instead of Host objects."""
229 # This implementation can be replaced with a more efficient one
230 # that does not query for entire host objects in the future.
231 return [host_obj.hostname for host_obj in
232 self.get_hosts(status=status, label=label, **dargs)]
233
234
235 def reverify_hosts(self, hostnames=(), status=None, label=None):
236 query_args = dict(locked=False,
237 aclgroup__users__login=self.user)
238 query_args.update(self._dict_for_host_query(hostnames=hostnames,
239 status=status,
240 label=label))
mbligh4e545a52009-12-19 05:30:39 +0000241 return self.run('reverify_hosts', **query_args)
242
243
mbligh67647152008-11-19 00:18:14 +0000244 def create_host(self, hostname, **dargs):
mbligh54459c72009-01-21 19:26:44 +0000245 id = self.run('add_host', hostname=hostname, **dargs)
mbligh67647152008-11-19 00:18:14 +0000246 return self.get_hosts(id=id)[0]
247
248
MK Ryuacf35922014-10-03 14:56:49 -0700249 def get_host_attribute(self, attr, **dargs):
250 host_attrs = self.run('get_host_attribute', attribute=attr, **dargs)
251 return [HostAttribute(self, a) for a in host_attrs]
252
253
Chris Masone8abb6fc2012-01-31 09:27:36 -0800254 def set_host_attribute(self, attr, val, **dargs):
255 self.run('set_host_attribute', attribute=attr, value=val, **dargs)
256
257
mbligh67647152008-11-19 00:18:14 +0000258 def get_labels(self, **dargs):
259 labels = self.run('get_labels', **dargs)
mbligh5280e3b2008-12-22 14:39:28 +0000260 return [Label(self, l) for l in labels]
mbligh67647152008-11-19 00:18:14 +0000261
262
263 def create_label(self, name, **dargs):
mbligh54459c72009-01-21 19:26:44 +0000264 id = self.run('add_label', name=name, **dargs)
mbligh67647152008-11-19 00:18:14 +0000265 return self.get_labels(id=id)[0]
266
267
268 def get_acls(self, **dargs):
269 acls = self.run('get_acl_groups', **dargs)
mbligh5280e3b2008-12-22 14:39:28 +0000270 return [Acl(self, a) for a in acls]
mbligh67647152008-11-19 00:18:14 +0000271
272
273 def create_acl(self, name, **dargs):
mbligh54459c72009-01-21 19:26:44 +0000274 id = self.run('add_acl_group', name=name, **dargs)
mbligh67647152008-11-19 00:18:14 +0000275 return self.get_acls(id=id)[0]
276
277
mbligh54459c72009-01-21 19:26:44 +0000278 def get_users(self, **dargs):
279 users = self.run('get_users', **dargs)
280 return [User(self, u) for u in users]
281
282
mbligh1354c9d2008-12-22 14:56:13 +0000283 def generate_control_file(self, tests, **dargs):
284 ret = self.run('generate_control_file', tests=tests, **dargs)
285 return ControlFile(self, ret)
286
287
mbligh67647152008-11-19 00:18:14 +0000288 def get_jobs(self, summary=False, **dargs):
289 if summary:
290 jobs_data = self.run('get_jobs_summary', **dargs)
291 else:
292 jobs_data = self.run('get_jobs', **dargs)
mblighafbba0c2009-06-08 16:44:45 +0000293 jobs = []
294 for j in jobs_data:
295 job = Job(self, j)
296 # Set up some extra information defaults
297 job.testname = re.sub('\s.*', '', job.name) # arbitrary default
298 job.platform_results = {}
299 job.platform_reasons = {}
300 jobs.append(job)
301 return jobs
mbligh67647152008-11-19 00:18:14 +0000302
303
304 def get_host_queue_entries(self, **data):
305 entries = self.run('get_host_queue_entries', **data)
mblighf9e35862009-02-26 01:03:11 +0000306 job_statuses = [JobStatus(self, e) for e in entries]
mbligh99b24f42009-06-08 16:45:55 +0000307
308 # Sadly, get_host_queue_entries doesn't return platforms, we have
309 # to get those back from an explicit get_hosts queury, then patch
310 # the new host objects back into the host list.
311 hostnames = [s.host.hostname for s in job_statuses if s.host]
312 host_hash = {}
313 for host in self.get_hosts(hostname__in=hostnames):
314 host_hash[host.hostname] = host
315 for status in job_statuses:
316 if status.host:
Fang Deng97dafbc2015-04-23 23:06:18 -0700317 status.host = host_hash.get(status.host.hostname)
mblighf9e35862009-02-26 01:03:11 +0000318 # filter job statuses that have either host or meta_host
319 return [status for status in job_statuses if (status.host or
320 status.meta_host)]
mbligh67647152008-11-19 00:18:14 +0000321
322
MK Ryu1b2d7f92015-02-24 17:45:02 -0800323 def get_special_tasks(self, **data):
324 tasks = self.run('get_special_tasks', **data)
325 return [SpecialTask(self, t) for t in tasks]
326
327
J. Richard Barnette9f10c9f2015-04-13 16:44:50 -0700328 def get_host_special_tasks(self, host_id, **data):
329 tasks = self.run('get_host_special_tasks',
330 host_id=host_id, **data)
331 return [SpecialTask(self, t) for t in tasks]
332
333
J. Richard Barnette8dbd6d32015-05-01 11:01:12 -0700334 def get_host_status_task(self, host_id, end_time):
335 task = self.run('get_host_status_task',
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700336 host_id=host_id, end_time=end_time)
J. Richard Barnettebc9a7952015-04-16 17:43:27 -0700337 return SpecialTask(self, task) if task else None
338
339
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700340 def get_host_diagnosis_interval(self, host_id, end_time, success):
341 return self.run('get_host_diagnosis_interval',
342 host_id=host_id, end_time=end_time,
343 success=success)
344
345
mblighb9db5162009-04-17 22:21:41 +0000346 def create_job_by_test(self, tests, kernel=None, use_container=False,
Eric Lie0493a42010-11-15 13:05:43 -0800347 kernel_cmdline=None, **dargs):
mbligh67647152008-11-19 00:18:14 +0000348 """
349 Given a test name, fetch the appropriate control file from the server
mbligh4e576612008-12-22 14:56:36 +0000350 and submit it.
351
Eric Lie0493a42010-11-15 13:05:43 -0800352 @param kernel: A comma separated list of kernel versions to boot.
353 @param kernel_cmdline: The command line used to boot all kernels listed
354 in the kernel parameter.
355
mbligh4e576612008-12-22 14:56:36 +0000356 Returns a list of job objects
mbligh67647152008-11-19 00:18:14 +0000357 """
mblighb9db5162009-04-17 22:21:41 +0000358 assert ('hosts' in dargs or
359 'atomic_group_name' in dargs and 'synch_count' in dargs)
showarda2cd72b2009-10-01 18:43:53 +0000360 if kernel:
361 kernel_list = re.split('[\s,]+', kernel.strip())
Eric Lie0493a42010-11-15 13:05:43 -0800362 kernel_info = []
363 for version in kernel_list:
364 kernel_dict = {'version': version}
365 if kernel_cmdline is not None:
366 kernel_dict['cmdline'] = kernel_cmdline
367 kernel_info.append(kernel_dict)
showarda2cd72b2009-10-01 18:43:53 +0000368 else:
369 kernel_info = None
370 control_file = self.generate_control_file(
Dale Curtis74a314b2011-06-23 14:55:46 -0700371 tests=tests, kernel=kernel_info, use_container=use_container)
mbligh1354c9d2008-12-22 14:56:13 +0000372 if control_file.is_server:
Aviv Keshet3dd8beb2013-05-13 17:36:04 -0700373 dargs['control_type'] = control_data.CONTROL_TYPE_NAMES.SERVER
mbligh67647152008-11-19 00:18:14 +0000374 else:
Aviv Keshet3dd8beb2013-05-13 17:36:04 -0700375 dargs['control_type'] = control_data.CONTROL_TYPE_NAMES.CLIENT
mbligh67647152008-11-19 00:18:14 +0000376 dargs['dependencies'] = dargs.get('dependencies', []) + \
mbligh1354c9d2008-12-22 14:56:13 +0000377 control_file.dependencies
378 dargs['control_file'] = control_file.control_file
mbligh672666c2009-07-28 23:22:13 +0000379 if not dargs.get('synch_count', None):
mblighc99fccf2009-07-11 00:59:33 +0000380 dargs['synch_count'] = control_file.synch_count
mblighb9db5162009-04-17 22:21:41 +0000381 if 'hosts' in dargs and len(dargs['hosts']) < dargs['synch_count']:
382 # will not be able to satisfy this request
mbligh38b09152009-04-28 18:34:25 +0000383 return None
384 return self.create_job(**dargs)
mbligh67647152008-11-19 00:18:14 +0000385
386
387 def create_job(self, control_file, name=' ', priority='Medium',
Aviv Keshet3dd8beb2013-05-13 17:36:04 -0700388 control_type=control_data.CONTROL_TYPE_NAMES.CLIENT, **dargs):
mbligh67647152008-11-19 00:18:14 +0000389 id = self.run('create_job', name=name, priority=priority,
390 control_file=control_file, control_type=control_type, **dargs)
391 return self.get_jobs(id=id)[0]
392
393
mbligh282ce892010-01-06 18:40:17 +0000394 def run_test_suites(self, pairings, kernel, kernel_label=None,
395 priority='Medium', wait=True, poll_interval=10,
Simran Basi7e605742013-11-12 13:43:36 -0800396 email_from=None, email_to=None, timeout_mins=10080,
Simran Basi34217022012-11-06 13:43:15 -0800397 max_runtime_mins=10080, kernel_cmdline=None):
mbligh5b618382008-12-03 15:24:01 +0000398 """
399 Run a list of test suites on a particular kernel.
mbligh1ef218d2009-08-03 16:57:56 +0000400
mbligh5b618382008-12-03 15:24:01 +0000401 Poll for them to complete, and return whether they worked or not.
mbligh1ef218d2009-08-03 16:57:56 +0000402
mbligh282ce892010-01-06 18:40:17 +0000403 @param pairings: List of MachineTestPairing objects to invoke.
404 @param kernel: Name of the kernel to run.
405 @param kernel_label: Label (string) of the kernel to run such as
406 '<kernel-version> : <config> : <date>'
407 If any pairing object has its job_label attribute set it
408 will override this value for that particular job.
Eric Lie0493a42010-11-15 13:05:43 -0800409 @param kernel_cmdline: The command line to boot the kernel(s) with.
mbligh282ce892010-01-06 18:40:17 +0000410 @param wait: boolean - Wait for the results to come back?
411 @param poll_interval: Interval between polling for job results (in mins)
412 @param email_from: Send notification email upon completion from here.
413 @param email_from: Send notification email upon completion to here.
mbligh5b618382008-12-03 15:24:01 +0000414 """
415 jobs = []
416 for pairing in pairings:
mbligh0c4f8d72009-05-12 20:52:18 +0000417 try:
418 new_job = self.invoke_test(pairing, kernel, kernel_label,
Simran Basi7e605742013-11-12 13:43:36 -0800419 priority, timeout_mins=timeout_mins,
Eric Lie0493a42010-11-15 13:05:43 -0800420 kernel_cmdline=kernel_cmdline,
Simran Basi34217022012-11-06 13:43:15 -0800421 max_runtime_mins=max_runtime_mins)
mbligh0c4f8d72009-05-12 20:52:18 +0000422 if not new_job:
423 continue
mbligh0c4f8d72009-05-12 20:52:18 +0000424 jobs.append(new_job)
425 except Exception, e:
426 traceback.print_exc()
mblighb9db5162009-04-17 22:21:41 +0000427 if not wait or not jobs:
mbligh5b618382008-12-03 15:24:01 +0000428 return
mbligh5280e3b2008-12-22 14:39:28 +0000429 tko = TKO()
mbligh5b618382008-12-03 15:24:01 +0000430 while True:
431 time.sleep(60 * poll_interval)
mbligh5280e3b2008-12-22 14:39:28 +0000432 result = self.poll_all_jobs(tko, jobs, email_from, email_to)
mbligh5b618382008-12-03 15:24:01 +0000433 if result is not None:
434 return result
435
436
mbligh45ffc432008-12-09 23:35:17 +0000437 def result_notify(self, job, email_from, email_to):
mbligh5b618382008-12-03 15:24:01 +0000438 """
mbligh45ffc432008-12-09 23:35:17 +0000439 Notify about the result of a job. Will always print, if email data
440 is provided, will send email for it as well.
441
442 job: job object to notify about
443 email_from: send notification email upon completion from here
444 email_from: send notification email upon completion to here
445 """
446 if job.result == True:
447 subject = 'Testing PASSED: '
448 else:
449 subject = 'Testing FAILED: '
450 subject += '%s : %s\n' % (job.name, job.id)
451 text = []
452 for platform in job.results_platform_map:
453 for status in job.results_platform_map[platform]:
454 if status == 'Total':
455 continue
mbligh451ede12009-02-12 21:54:03 +0000456 for host in job.results_platform_map[platform][status]:
457 text.append('%20s %10s %10s' % (platform, status, host))
458 if status == 'Failed':
459 for test_status in job.test_status[host].fail:
460 text.append('(%s, %s) : %s' % \
461 (host, test_status.test_name,
462 test_status.reason))
463 text.append('')
mbligh37eceaa2008-12-15 22:56:37 +0000464
mbligh451ede12009-02-12 21:54:03 +0000465 base_url = 'http://' + self.server
mbligh37eceaa2008-12-15 22:56:37 +0000466
467 params = ('columns=test',
468 'rows=machine_group',
469 "condition=tag~'%s-%%25'" % job.id,
470 'title=Report')
471 query_string = '&'.join(params)
mbligh451ede12009-02-12 21:54:03 +0000472 url = '%s/tko/compose_query.cgi?%s' % (base_url, query_string)
473 text.append(url + '\n')
474 url = '%s/afe/#tab_id=view_job&object_id=%s' % (base_url, job.id)
475 text.append(url + '\n')
mbligh37eceaa2008-12-15 22:56:37 +0000476
477 body = '\n'.join(text)
478 print '---------------------------------------------------'
479 print 'Subject: ', subject
mbligh45ffc432008-12-09 23:35:17 +0000480 print body
mbligh37eceaa2008-12-15 22:56:37 +0000481 print '---------------------------------------------------'
mbligh45ffc432008-12-09 23:35:17 +0000482 if email_from and email_to:
mbligh37eceaa2008-12-15 22:56:37 +0000483 print 'Sending email ...'
mbligh45ffc432008-12-09 23:35:17 +0000484 utils.send_email(email_from, email_to, subject, body)
485 print
mbligh37eceaa2008-12-15 22:56:37 +0000486
mbligh45ffc432008-12-09 23:35:17 +0000487
mbligh1354c9d2008-12-22 14:56:13 +0000488 def print_job_result(self, job):
489 """
490 Print the result of a single job.
491 job: a job object
492 """
493 if job.result is None:
494 print 'PENDING',
495 elif job.result == True:
496 print 'PASSED',
497 elif job.result == False:
498 print 'FAILED',
mbligh912c3f32009-03-25 19:31:30 +0000499 elif job.result == "Abort":
500 print 'ABORT',
mbligh1354c9d2008-12-22 14:56:13 +0000501 print ' %s : %s' % (job.id, job.name)
502
503
mbligh451ede12009-02-12 21:54:03 +0000504 def poll_all_jobs(self, tko, jobs, email_from=None, email_to=None):
mbligh45ffc432008-12-09 23:35:17 +0000505 """
506 Poll all jobs in a list.
507 jobs: list of job objects to poll
508 email_from: send notification email upon completion from here
509 email_from: send notification email upon completion to here
510
511 Returns:
mbligh5b618382008-12-03 15:24:01 +0000512 a) All complete successfully (return True)
513 b) One or more has failed (return False)
514 c) Cannot tell yet (return None)
515 """
mbligh45ffc432008-12-09 23:35:17 +0000516 results = []
mbligh5b618382008-12-03 15:24:01 +0000517 for job in jobs:
mbligh676dcbe2009-06-15 21:57:27 +0000518 if getattr(job, 'result', None) is None:
Chris Masone6fed6462011-10-20 16:36:43 -0700519 job.result = self.poll_job_results(tko, job)
mbligh676dcbe2009-06-15 21:57:27 +0000520 if job.result is not None:
521 self.result_notify(job, email_from, email_to)
mbligh45ffc432008-12-09 23:35:17 +0000522
mbligh676dcbe2009-06-15 21:57:27 +0000523 results.append(job.result)
mbligh1354c9d2008-12-22 14:56:13 +0000524 self.print_job_result(job)
mbligh45ffc432008-12-09 23:35:17 +0000525
526 if None in results:
527 return None
mbligh912c3f32009-03-25 19:31:30 +0000528 elif False in results or "Abort" in results:
mbligh45ffc432008-12-09 23:35:17 +0000529 return False
530 else:
531 return True
mbligh5b618382008-12-03 15:24:01 +0000532
533
mbligh1f23f362008-12-22 14:46:12 +0000534 def _included_platform(self, host, platforms):
535 """
536 See if host's platforms matches any of the patterns in the included
537 platforms list.
538 """
539 if not platforms:
540 return True # No filtering of platforms
541 for platform in platforms:
542 if re.search(platform, host.platform):
543 return True
544 return False
545
546
mbligh7b312282009-01-07 16:45:43 +0000547 def invoke_test(self, pairing, kernel, kernel_label, priority='Medium',
Eric Lie0493a42010-11-15 13:05:43 -0800548 kernel_cmdline=None, **dargs):
mbligh5b618382008-12-03 15:24:01 +0000549 """
550 Given a pairing of a control file to a machine label, find all machines
551 with that label, and submit that control file to them.
mbligh1ef218d2009-08-03 16:57:56 +0000552
mbligh282ce892010-01-06 18:40:17 +0000553 @param kernel_label: Label (string) of the kernel to run such as
554 '<kernel-version> : <config> : <date>'
555 If any pairing object has its job_label attribute set it
556 will override this value for that particular job.
557
558 @returns A list of job objects.
mbligh5b618382008-12-03 15:24:01 +0000559 """
mbligh282ce892010-01-06 18:40:17 +0000560 # The pairing can override the job label.
561 if pairing.job_label:
562 kernel_label = pairing.job_label
mbligh5b618382008-12-03 15:24:01 +0000563 job_name = '%s : %s' % (pairing.machine_label, kernel_label)
564 hosts = self.get_hosts(multiple_labels=[pairing.machine_label])
mbligh1f23f362008-12-22 14:46:12 +0000565 platforms = pairing.platforms
566 hosts = [h for h in hosts if self._included_platform(h, platforms)]
mblighc2847b72009-03-25 19:32:20 +0000567 dead_statuses = self.host_statuses(live=False)
568 host_list = [h.hostname for h in hosts if h.status not in dead_statuses]
mbligh1f23f362008-12-22 14:46:12 +0000569 print 'HOSTS: %s' % host_list
mblighb9db5162009-04-17 22:21:41 +0000570 if pairing.atomic_group_sched:
mblighc99fccf2009-07-11 00:59:33 +0000571 dargs['synch_count'] = pairing.synch_count
mblighb9db5162009-04-17 22:21:41 +0000572 dargs['atomic_group_name'] = pairing.machine_label
573 else:
574 dargs['hosts'] = host_list
mbligh38b09152009-04-28 18:34:25 +0000575 new_job = self.create_job_by_test(name=job_name,
mbligh17c75e62009-06-08 16:18:21 +0000576 dependencies=[pairing.machine_label],
577 tests=[pairing.control_file],
578 priority=priority,
579 kernel=kernel,
Eric Lie0493a42010-11-15 13:05:43 -0800580 kernel_cmdline=kernel_cmdline,
mbligh17c75e62009-06-08 16:18:21 +0000581 use_container=pairing.container,
582 **dargs)
mbligh38b09152009-04-28 18:34:25 +0000583 if new_job:
mbligh17c75e62009-06-08 16:18:21 +0000584 if pairing.testname:
585 new_job.testname = pairing.testname
mbligh4e576612008-12-22 14:56:36 +0000586 print 'Invoked test %s : %s' % (new_job.id, job_name)
mbligh38b09152009-04-28 18:34:25 +0000587 return new_job
mbligh5b618382008-12-03 15:24:01 +0000588
589
mblighb9db5162009-04-17 22:21:41 +0000590 def _job_test_results(self, tko, job, debug, tests=[]):
mbligh5b618382008-12-03 15:24:01 +0000591 """
mbligh5280e3b2008-12-22 14:39:28 +0000592 Retrieve test results for a job
mbligh5b618382008-12-03 15:24:01 +0000593 """
mbligh5280e3b2008-12-22 14:39:28 +0000594 job.test_status = {}
595 try:
596 test_statuses = tko.get_status_counts(job=job.id)
597 except Exception:
598 print "Ignoring exception on poll job; RPC interface is flaky"
599 traceback.print_exc()
600 return
601
602 for test_status in test_statuses:
mbligh7479a182009-01-07 16:46:24 +0000603 # SERVER_JOB is buggy, and often gives false failures. Ignore it.
604 if test_status.test_name == 'SERVER_JOB':
605 continue
mblighb9db5162009-04-17 22:21:41 +0000606 # if tests is not empty, restrict list of test_statuses to tests
607 if tests and test_status.test_name not in tests:
608 continue
mbligh451ede12009-02-12 21:54:03 +0000609 if debug:
610 print test_status
mbligh5280e3b2008-12-22 14:39:28 +0000611 hostname = test_status.hostname
612 if hostname not in job.test_status:
613 job.test_status[hostname] = TestResults()
614 job.test_status[hostname].add(test_status)
615
616
mbligh451ede12009-02-12 21:54:03 +0000617 def _job_results_platform_map(self, job, debug):
mblighc9e427e2009-04-28 18:35:06 +0000618 # Figure out which hosts passed / failed / aborted in a job
619 # Creates a 2-dimensional hash, stored as job.results_platform_map
620 # 1st index - platform type (string)
621 # 2nd index - Status (string)
622 # 'Completed' / 'Failed' / 'Aborted'
623 # Data indexed by this hash is a list of hostnames (text strings)
mbligh5280e3b2008-12-22 14:39:28 +0000624 job.results_platform_map = {}
mbligh5b618382008-12-03 15:24:01 +0000625 try:
mbligh45ffc432008-12-09 23:35:17 +0000626 job_statuses = self.get_host_queue_entries(job=job.id)
mbligh5b618382008-12-03 15:24:01 +0000627 except Exception:
628 print "Ignoring exception on poll job; RPC interface is flaky"
629 traceback.print_exc()
630 return None
mbligh5280e3b2008-12-22 14:39:28 +0000631
mbligh5b618382008-12-03 15:24:01 +0000632 platform_map = {}
mbligh5280e3b2008-12-22 14:39:28 +0000633 job.job_status = {}
mbligh451ede12009-02-12 21:54:03 +0000634 job.metahost_index = {}
mbligh5b618382008-12-03 15:24:01 +0000635 for job_status in job_statuses:
mblighc9e427e2009-04-28 18:35:06 +0000636 # This is basically "for each host / metahost in the job"
mbligh451ede12009-02-12 21:54:03 +0000637 if job_status.host:
638 hostname = job_status.host.hostname
639 else: # This is a metahost
640 metahost = job_status.meta_host
641 index = job.metahost_index.get(metahost, 1)
642 job.metahost_index[metahost] = index + 1
643 hostname = '%s.%s' % (metahost, index)
mbligh5280e3b2008-12-22 14:39:28 +0000644 job.job_status[hostname] = job_status.status
mbligh5b618382008-12-03 15:24:01 +0000645 status = job_status.status
mbligh0ecbe632009-05-13 21:34:56 +0000646 # Skip hosts that failed verify or repair:
647 # that's a machine failure, not a job failure
mbligh451ede12009-02-12 21:54:03 +0000648 if hostname in job.test_status:
649 verify_failed = False
650 for failure in job.test_status[hostname].fail:
mbligh0ecbe632009-05-13 21:34:56 +0000651 if (failure.test_name == 'verify' or
652 failure.test_name == 'repair'):
mbligh451ede12009-02-12 21:54:03 +0000653 verify_failed = True
654 break
655 if verify_failed:
656 continue
mblighc9e427e2009-04-28 18:35:06 +0000657 if hostname in job.test_status and job.test_status[hostname].fail:
658 # If the any tests failed in the job, we want to mark the
659 # job result as failed, overriding the default job status.
660 if status != "Aborted": # except if it's an aborted job
661 status = 'Failed'
mbligh451ede12009-02-12 21:54:03 +0000662 if job_status.host:
663 platform = job_status.host.platform
664 else: # This is a metahost
665 platform = job_status.meta_host
mbligh5b618382008-12-03 15:24:01 +0000666 if platform not in platform_map:
667 platform_map[platform] = {'Total' : [hostname]}
668 else:
669 platform_map[platform]['Total'].append(hostname)
670 new_host_list = platform_map[platform].get(status, []) + [hostname]
671 platform_map[platform][status] = new_host_list
mbligh45ffc432008-12-09 23:35:17 +0000672 job.results_platform_map = platform_map
mbligh5280e3b2008-12-22 14:39:28 +0000673
674
mbligh17c75e62009-06-08 16:18:21 +0000675 def set_platform_results(self, test_job, platform, result):
676 """
677 Result must be None, 'FAIL', 'WARN' or 'GOOD'
678 """
679 if test_job.platform_results[platform] is not None:
680 # We're already done, and results recorded. This can't change later.
681 return
682 test_job.platform_results[platform] = result
683 # Note that self.job refers to the metajob we're IN, not the job
684 # that we're excuting from here.
685 testname = '%s.%s' % (test_job.testname, platform)
686 if self.job:
687 self.job.record(result, None, testname, status='')
688
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800689
Chris Masone6fed6462011-10-20 16:36:43 -0700690 def poll_job_results(self, tko, job, enough=1, debug=False):
mbligh5280e3b2008-12-22 14:39:28 +0000691 """
Chris Masone3a560bd2011-11-14 16:53:56 -0800692 Analyse all job results by platform
mbligh1ef218d2009-08-03 16:57:56 +0000693
Chris Masone3a560bd2011-11-14 16:53:56 -0800694 params:
695 tko: a TKO object representing the results DB.
696 job: the job to be examined.
Chris Masone6fed6462011-10-20 16:36:43 -0700697 enough: the acceptable delta between the number of completed
698 tests and the total number of tests.
Chris Masone3a560bd2011-11-14 16:53:56 -0800699 debug: enable debugging output.
700
701 returns:
Chris Masone6fed6462011-10-20 16:36:43 -0700702 False: if any platform has more than |enough| failures
703 None: if any platform has less than |enough| machines
Chris Masone3a560bd2011-11-14 16:53:56 -0800704 not yet Good.
Chris Masone6fed6462011-10-20 16:36:43 -0700705 True: if all platforms have at least |enough| machines
Chris Masone3a560bd2011-11-14 16:53:56 -0800706 Good.
mbligh5280e3b2008-12-22 14:39:28 +0000707 """
mbligh451ede12009-02-12 21:54:03 +0000708 self._job_test_results(tko, job, debug)
mblighe7fcf562009-05-21 01:43:17 +0000709 if job.test_status == {}:
710 return None
mbligh451ede12009-02-12 21:54:03 +0000711 self._job_results_platform_map(job, debug)
mbligh5280e3b2008-12-22 14:39:28 +0000712
mbligh5b618382008-12-03 15:24:01 +0000713 good_platforms = []
mbligh912c3f32009-03-25 19:31:30 +0000714 failed_platforms = []
715 aborted_platforms = []
mbligh5b618382008-12-03 15:24:01 +0000716 unknown_platforms = []
mbligh5280e3b2008-12-22 14:39:28 +0000717 platform_map = job.results_platform_map
mbligh5b618382008-12-03 15:24:01 +0000718 for platform in platform_map:
mbligh17c75e62009-06-08 16:18:21 +0000719 if not job.platform_results.has_key(platform):
720 # record test start, but there's no way to do this right now
721 job.platform_results[platform] = None
mbligh5b618382008-12-03 15:24:01 +0000722 total = len(platform_map[platform]['Total'])
723 completed = len(platform_map[platform].get('Completed', []))
mbligh912c3f32009-03-25 19:31:30 +0000724 failed = len(platform_map[platform].get('Failed', []))
725 aborted = len(platform_map[platform].get('Aborted', []))
mbligh17c75e62009-06-08 16:18:21 +0000726
mbligh1ef218d2009-08-03 16:57:56 +0000727 # We set up what we want to record here, but don't actually do
mbligh17c75e62009-06-08 16:18:21 +0000728 # it yet, until we have a decisive answer for this platform
729 if aborted or failed:
730 bad = aborted + failed
731 if (bad > 1) or (bad * 2 >= total):
732 platform_test_result = 'FAIL'
733 else:
734 platform_test_result = 'WARN'
735
Chris Masone6fed6462011-10-20 16:36:43 -0700736 if aborted > enough:
mbligh912c3f32009-03-25 19:31:30 +0000737 aborted_platforms.append(platform)
mbligh17c75e62009-06-08 16:18:21 +0000738 self.set_platform_results(job, platform, platform_test_result)
Chris Masone6fed6462011-10-20 16:36:43 -0700739 elif (failed * 2 >= total) or (failed > enough):
mbligh912c3f32009-03-25 19:31:30 +0000740 failed_platforms.append(platform)
mbligh17c75e62009-06-08 16:18:21 +0000741 self.set_platform_results(job, platform, platform_test_result)
Chris Masone6fed6462011-10-20 16:36:43 -0700742 elif (completed >= enough) and (completed + enough >= total):
mbligh5b618382008-12-03 15:24:01 +0000743 good_platforms.append(platform)
mbligh17c75e62009-06-08 16:18:21 +0000744 self.set_platform_results(job, platform, 'GOOD')
mbligh5b618382008-12-03 15:24:01 +0000745 else:
746 unknown_platforms.append(platform)
747 detail = []
748 for status in platform_map[platform]:
749 if status == 'Total':
750 continue
751 detail.append('%s=%s' % (status,platform_map[platform][status]))
752 if debug:
mbligh1ef218d2009-08-03 16:57:56 +0000753 print '%20s %d/%d %s' % (platform, completed, total,
mbligh5b618382008-12-03 15:24:01 +0000754 ' '.join(detail))
755 print
mbligh1ef218d2009-08-03 16:57:56 +0000756
mbligh912c3f32009-03-25 19:31:30 +0000757 if len(aborted_platforms) > 0:
mbligh5b618382008-12-03 15:24:01 +0000758 if debug:
mbligh17c75e62009-06-08 16:18:21 +0000759 print 'Result aborted - platforms: ',
760 print ' '.join(aborted_platforms)
mbligh912c3f32009-03-25 19:31:30 +0000761 return "Abort"
762 if len(failed_platforms) > 0:
763 if debug:
764 print 'Result bad - platforms: ' + ' '.join(failed_platforms)
mbligh5b618382008-12-03 15:24:01 +0000765 return False
766 if len(unknown_platforms) > 0:
767 if debug:
768 platform_list = ' '.join(unknown_platforms)
769 print 'Result unknown - platforms: ', platform_list
770 return None
771 if debug:
772 platform_list = ' '.join(good_platforms)
773 print 'Result good - all platforms passed: ', platform_list
774 return True
775
776
Simran Basi01984f52015-10-12 15:36:45 -0700777 def abort_jobs(self, jobs):
778 """Abort a list of jobs.
779
780 Already completed jobs will not be affected.
781
782 @param jobs: List of job ids to abort.
783 """
784 for job in jobs:
785 self.run('abort_host_queue_entries', job_id=job)
786
787
mbligh5280e3b2008-12-22 14:39:28 +0000788class TestResults(object):
789 """
790 Container class used to hold the results of the tests for a job
791 """
792 def __init__(self):
793 self.good = []
794 self.fail = []
mbligh451ede12009-02-12 21:54:03 +0000795 self.pending = []
mbligh5280e3b2008-12-22 14:39:28 +0000796
797
798 def add(self, result):
mbligh451ede12009-02-12 21:54:03 +0000799 if result.complete_count > result.pass_count:
800 self.fail.append(result)
801 elif result.incomplete_count > 0:
802 self.pending.append(result)
mbligh5280e3b2008-12-22 14:39:28 +0000803 else:
mbligh451ede12009-02-12 21:54:03 +0000804 self.good.append(result)
mbligh5280e3b2008-12-22 14:39:28 +0000805
806
807class RpcObject(object):
mbligh67647152008-11-19 00:18:14 +0000808 """
809 Generic object used to construct python objects from rpc calls
810 """
811 def __init__(self, afe, hash):
812 self.afe = afe
813 self.hash = hash
814 self.__dict__.update(hash)
815
816
817 def __str__(self):
818 return dump_object(self.__repr__(), self)
819
820
mbligh1354c9d2008-12-22 14:56:13 +0000821class ControlFile(RpcObject):
822 """
823 AFE control file object
824
825 Fields: synch_count, dependencies, control_file, is_server
826 """
827 def __repr__(self):
828 return 'CONTROL FILE: %s' % self.control_file
829
830
mbligh5280e3b2008-12-22 14:39:28 +0000831class Label(RpcObject):
mbligh67647152008-11-19 00:18:14 +0000832 """
833 AFE label object
834
835 Fields:
836 name, invalid, platform, kernel_config, id, only_if_needed
837 """
838 def __repr__(self):
839 return 'LABEL: %s' % self.name
840
841
842 def add_hosts(self, hosts):
Chris Masone3a560bd2011-11-14 16:53:56 -0800843 return self.afe.run('label_add_hosts', id=self.id, hosts=hosts)
mbligh67647152008-11-19 00:18:14 +0000844
845
846 def remove_hosts(self, hosts):
Chris Masone3a560bd2011-11-14 16:53:56 -0800847 return self.afe.run('label_remove_hosts', id=self.id, hosts=hosts)
mbligh67647152008-11-19 00:18:14 +0000848
849
mbligh5280e3b2008-12-22 14:39:28 +0000850class Acl(RpcObject):
mbligh67647152008-11-19 00:18:14 +0000851 """
852 AFE acl object
853
854 Fields:
855 users, hosts, description, name, id
856 """
857 def __repr__(self):
858 return 'ACL: %s' % self.name
859
860
861 def add_hosts(self, hosts):
862 self.afe.log('Adding hosts %s to ACL %s' % (hosts, self.name))
863 return self.afe.run('acl_group_add_hosts', self.id, hosts)
864
865
866 def remove_hosts(self, hosts):
867 self.afe.log('Removing hosts %s from ACL %s' % (hosts, self.name))
868 return self.afe.run('acl_group_remove_hosts', self.id, hosts)
869
870
mbligh54459c72009-01-21 19:26:44 +0000871 def add_users(self, users):
872 self.afe.log('Adding users %s to ACL %s' % (users, self.name))
873 return self.afe.run('acl_group_add_users', id=self.name, users=users)
874
875
mbligh5280e3b2008-12-22 14:39:28 +0000876class Job(RpcObject):
mbligh67647152008-11-19 00:18:14 +0000877 """
878 AFE job object
879
880 Fields:
881 name, control_file, control_type, synch_count, reboot_before,
882 run_verify, priority, email_list, created_on, dependencies,
883 timeout, owner, reboot_after, id
884 """
885 def __repr__(self):
886 return 'JOB: %s' % self.id
887
888
mbligh5280e3b2008-12-22 14:39:28 +0000889class JobStatus(RpcObject):
mbligh67647152008-11-19 00:18:14 +0000890 """
891 AFE job_status object
892
893 Fields:
894 status, complete, deleted, meta_host, host, active, execution_subdir, id
895 """
896 def __init__(self, afe, hash):
MK Ryu1b2d7f92015-02-24 17:45:02 -0800897 super(JobStatus, self).__init__(afe, hash)
mbligh5280e3b2008-12-22 14:39:28 +0000898 self.job = Job(afe, self.job)
Dale Curtis8adf7892011-09-08 16:13:36 -0700899 if getattr(self, 'host'):
mbligh99b24f42009-06-08 16:45:55 +0000900 self.host = Host(afe, self.host)
mbligh67647152008-11-19 00:18:14 +0000901
902
903 def __repr__(self):
mbligh451ede12009-02-12 21:54:03 +0000904 if self.host and self.host.hostname:
905 hostname = self.host.hostname
906 else:
907 hostname = 'None'
908 return 'JOB STATUS: %s-%s' % (self.job.id, hostname)
mbligh67647152008-11-19 00:18:14 +0000909
910
MK Ryu1b2d7f92015-02-24 17:45:02 -0800911class SpecialTask(RpcObject):
912 """
913 AFE special task object
914 """
915 def __init__(self, afe, hash):
916 super(SpecialTask, self).__init__(afe, hash)
917 self.host = Host(afe, self.host)
918
919
920 def __repr__(self):
921 return 'SPECIAL TASK: %s' % self.id
922
923
mbligh5280e3b2008-12-22 14:39:28 +0000924class Host(RpcObject):
mbligh67647152008-11-19 00:18:14 +0000925 """
926 AFE host object
927
928 Fields:
929 status, lock_time, locked_by, locked, hostname, invalid,
930 synch_id, labels, platform, protection, dirty, id
931 """
932 def __repr__(self):
933 return 'HOST OBJECT: %s' % self.hostname
934
935
936 def show(self):
937 labels = list(set(self.labels) - set([self.platform]))
938 print '%-6s %-7s %-7s %-16s %s' % (self.hostname, self.status,
939 self.locked, self.platform,
940 ', '.join(labels))
941
942
mbligh54459c72009-01-21 19:26:44 +0000943 def delete(self):
944 return self.afe.run('delete_host', id=self.id)
945
946
mbligh6463c4b2009-01-30 00:33:37 +0000947 def modify(self, **dargs):
948 return self.afe.run('modify_host', id=self.id, **dargs)
949
950
mbligh67647152008-11-19 00:18:14 +0000951 def get_acls(self):
952 return self.afe.get_acls(hosts__hostname=self.hostname)
953
954
955 def add_acl(self, acl_name):
956 self.afe.log('Adding ACL %s to host %s' % (acl_name, self.hostname))
957 return self.afe.run('acl_group_add_hosts', id=acl_name,
958 hosts=[self.hostname])
959
960
961 def remove_acl(self, acl_name):
962 self.afe.log('Removing ACL %s from host %s' % (acl_name, self.hostname))
963 return self.afe.run('acl_group_remove_hosts', id=acl_name,
964 hosts=[self.hostname])
965
966
967 def get_labels(self):
968 return self.afe.get_labels(host__hostname__in=[self.hostname])
969
970
971 def add_labels(self, labels):
972 self.afe.log('Adding labels %s to host %s' % (labels, self.hostname))
973 return self.afe.run('host_add_labels', id=self.id, labels=labels)
974
975
976 def remove_labels(self, labels):
977 self.afe.log('Removing labels %s from host %s' % (labels,self.hostname))
978 return self.afe.run('host_remove_labels', id=self.id, labels=labels)
mbligh5b618382008-12-03 15:24:01 +0000979
980
mbligh54459c72009-01-21 19:26:44 +0000981class User(RpcObject):
982 def __repr__(self):
983 return 'USER: %s' % self.login
984
985
mbligh5280e3b2008-12-22 14:39:28 +0000986class TestStatus(RpcObject):
mblighc31e4022008-12-11 19:32:30 +0000987 """
988 TKO test status object
989
990 Fields:
991 test_idx, hostname, testname, id
992 complete_count, incomplete_count, group_count, pass_count
993 """
994 def __repr__(self):
995 return 'TEST STATUS: %s' % self.id
996
997
MK Ryuacf35922014-10-03 14:56:49 -0700998class HostAttribute(RpcObject):
999 """
1000 AFE host attribute object
1001
1002 Fields:
1003 id, host, attribute, value
1004 """
1005 def __repr__(self):
1006 return 'HOST ATTRIBUTE %d' % self.id
1007
1008
mbligh5b618382008-12-03 15:24:01 +00001009class MachineTestPairing(object):
1010 """
1011 Object representing the pairing of a machine label with a control file
mbligh1f23f362008-12-22 14:46:12 +00001012
1013 machine_label: use machines from this label
1014 control_file: use this control file (by name in the frontend)
1015 platforms: list of rexeps to filter platforms by. [] => no filtering
mbligh282ce892010-01-06 18:40:17 +00001016 job_label: The label (name) to give to the autotest job launched
1017 to run this pairing. '<kernel-version> : <config> : <date>'
mbligh5b618382008-12-03 15:24:01 +00001018 """
mbligh1354c9d2008-12-22 14:56:13 +00001019 def __init__(self, machine_label, control_file, platforms=[],
mbligh17c75e62009-06-08 16:18:21 +00001020 container=False, atomic_group_sched=False, synch_count=0,
mbligh282ce892010-01-06 18:40:17 +00001021 testname=None, job_label=None):
mbligh5b618382008-12-03 15:24:01 +00001022 self.machine_label = machine_label
1023 self.control_file = control_file
mbligh1f23f362008-12-22 14:46:12 +00001024 self.platforms = platforms
mbligh1354c9d2008-12-22 14:56:13 +00001025 self.container = container
mblighb9db5162009-04-17 22:21:41 +00001026 self.atomic_group_sched = atomic_group_sched
1027 self.synch_count = synch_count
mbligh17c75e62009-06-08 16:18:21 +00001028 self.testname = testname
mbligh282ce892010-01-06 18:40:17 +00001029 self.job_label = job_label
mbligh1354c9d2008-12-22 14:56:13 +00001030
1031
1032 def __repr__(self):
1033 return '%s %s %s %s' % (self.machine_label, self.control_file,
1034 self.platforms, self.container)