blob: 7820a5ffb14411b87c5de09be6e809432b0eaaed [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
Richard Barnetteb9b37982016-05-20 19:23:39 -070016#pylint: disable=missing-docstring
17
Dan Shie8e0c052015-09-01 00:27:27 -070018import getpass
19import os
20import re
Dan Shie8e0c052015-09-01 00:27:27 -070021
mbligh67647152008-11-19 00:18:14 +000022import common
Allen Li327e6fd2016-11-22 13:45:41 -080023
mbligh67647152008-11-19 00:18:14 +000024from autotest_lib.frontend.afe import rpc_client_lib
Dan Shie8e0c052015-09-01 00:27:27 -070025from autotest_lib.client.common_lib import control_data
mbligh37eceaa2008-12-15 22:56:37 +000026from autotest_lib.client.common_lib import global_config
Allen Li352b86a2016-12-14 12:11:27 -080027from autotest_lib.client.common_lib import priorities
mbligh67647152008-11-19 00:18:14 +000028from autotest_lib.client.common_lib import utils
Scott Zawalski63470dd2012-09-05 00:49:43 -040029from autotest_lib.tko import db
30
Dan Shi5e2efb72017-02-07 11:40:23 -080031try:
32 from chromite.lib import metrics
33except ImportError:
34 metrics = utils.metrics_mock
Scott Zawalski63470dd2012-09-05 00:49:43 -040035
mbligh4e576612008-12-22 14:56:36 +000036try:
37 from autotest_lib.server.site_common import site_utils as server_utils
38except:
39 from autotest_lib.server import utils as server_utils
40form_ntuples_from_machines = server_utils.form_ntuples_from_machines
mbligh67647152008-11-19 00:18:14 +000041
mbligh37eceaa2008-12-15 22:56:37 +000042GLOBAL_CONFIG = global_config.global_config
43DEFAULT_SERVER = 'autotest'
44
Dan Shie8e0c052015-09-01 00:27:27 -070045
mbligh67647152008-11-19 00:18:14 +000046def dump_object(header, obj):
47 """
48 Standard way to print out the frontend objects (eg job, host, acl, label)
49 in a human-readable fashion for debugging
50 """
51 result = header + '\n'
52 for key in obj.hash:
53 if key == 'afe' or key == 'hash':
54 continue
55 result += '%20s: %s\n' % (key, obj.hash[key])
56 return result
57
58
mbligh5280e3b2008-12-22 14:39:28 +000059class RpcClient(object):
mbligh67647152008-11-19 00:18:14 +000060 """
mbligh451ede12009-02-12 21:54:03 +000061 Abstract RPC class for communicating with the autotest frontend
62 Inherited for both TKO and AFE uses.
mbligh67647152008-11-19 00:18:14 +000063
mbligh1ef218d2009-08-03 16:57:56 +000064 All the constructors go in the afe / tko class.
mbligh451ede12009-02-12 21:54:03 +000065 Manipulating methods go in the object classes themselves
mbligh67647152008-11-19 00:18:14 +000066 """
mbligh99b24f42009-06-08 16:45:55 +000067 def __init__(self, path, user, server, print_log, debug, reply_debug):
mbligh67647152008-11-19 00:18:14 +000068 """
mbligh451ede12009-02-12 21:54:03 +000069 Create a cached instance of a connection to the frontend
mbligh67647152008-11-19 00:18:14 +000070
71 user: username to connect as
mbligh451ede12009-02-12 21:54:03 +000072 server: frontend server to connect to
mbligh67647152008-11-19 00:18:14 +000073 print_log: pring a logging message to stdout on every operation
74 debug: print out all RPC traffic
75 """
Dan Shiff78f112015-06-12 13:34:02 -070076 if not user and utils.is_in_container():
77 user = GLOBAL_CONFIG.get_config_value('SSP', 'user', default=None)
mblighc31e4022008-12-11 19:32:30 +000078 if not user:
mblighdb59e3c2009-11-21 01:45:18 +000079 user = getpass.getuser()
mbligh451ede12009-02-12 21:54:03 +000080 if not server:
mbligh475f7762009-01-30 00:34:04 +000081 if 'AUTOTEST_WEB' in os.environ:
mbligh451ede12009-02-12 21:54:03 +000082 server = os.environ['AUTOTEST_WEB']
mbligh475f7762009-01-30 00:34:04 +000083 else:
mbligh451ede12009-02-12 21:54:03 +000084 server = GLOBAL_CONFIG.get_config_value('SERVER', 'hostname',
85 default=DEFAULT_SERVER)
86 self.server = server
mbligh67647152008-11-19 00:18:14 +000087 self.user = user
88 self.print_log = print_log
89 self.debug = debug
mbligh99b24f42009-06-08 16:45:55 +000090 self.reply_debug = reply_debug
Scott Zawalski347aaf42012-04-03 16:33:00 -040091 headers = {'AUTHORIZATION': self.user}
92 rpc_server = 'http://' + server + path
mbligh1354c9d2008-12-22 14:56:13 +000093 if debug:
94 print 'SERVER: %s' % rpc_server
95 print 'HEADERS: %s' % headers
mbligh67647152008-11-19 00:18:14 +000096 self.proxy = rpc_client_lib.get_proxy(rpc_server, headers=headers)
97
98
99 def run(self, call, **dargs):
100 """
101 Make a RPC call to the AFE server
102 """
103 rpc_call = getattr(self.proxy, call)
104 if self.debug:
105 print 'DEBUG: %s %s' % (call, dargs)
mbligh451ede12009-02-12 21:54:03 +0000106 try:
mbligh99b24f42009-06-08 16:45:55 +0000107 result = utils.strip_unicode(rpc_call(**dargs))
108 if self.reply_debug:
109 print result
110 return result
mbligh451ede12009-02-12 21:54:03 +0000111 except Exception:
mbligh451ede12009-02-12 21:54:03 +0000112 raise
mbligh67647152008-11-19 00:18:14 +0000113
114
115 def log(self, message):
116 if self.print_log:
117 print message
118
119
jamesrenc3940222010-02-19 21:57:37 +0000120class Planner(RpcClient):
121 def __init__(self, user=None, server=None, print_log=True, debug=False,
122 reply_debug=False):
123 super(Planner, self).__init__(path='/planner/server/rpc/',
124 user=user,
125 server=server,
126 print_log=print_log,
127 debug=debug,
128 reply_debug=reply_debug)
129
130
mbligh5280e3b2008-12-22 14:39:28 +0000131class TKO(RpcClient):
mbligh99b24f42009-06-08 16:45:55 +0000132 def __init__(self, user=None, server=None, print_log=True, debug=False,
133 reply_debug=False):
Scott Zawalski347aaf42012-04-03 16:33:00 -0400134 super(TKO, self).__init__(path='/new_tko/server/noauth/rpc/',
mbligh99b24f42009-06-08 16:45:55 +0000135 user=user,
136 server=server,
137 print_log=print_log,
138 debug=debug,
139 reply_debug=reply_debug)
Scott Zawalski63470dd2012-09-05 00:49:43 -0400140 self._db = None
141
142
Allen Li327e6fd2016-11-22 13:45:41 -0800143 @metrics.SecondsTimerDecorator(
Prathmesh Prabhua7556a92017-02-01 14:18:41 -0800144 'chromeos/autotest/tko/get_job_status_duration')
Scott Zawalski63470dd2012-09-05 00:49:43 -0400145 def get_job_test_statuses_from_db(self, job_id):
146 """Get job test statuses from the database.
147
148 Retrieve a set of fields from a job that reflect the status of each test
149 run within a job.
150 fields retrieved: status, test_name, reason, test_started_time,
151 test_finished_time, afe_job_id, job_owner, hostname.
152
153 @param job_id: The afe job id to look up.
154 @returns a TestStatus object of the resulting information.
155 """
156 if self._db is None:
Dan Shie8e0c052015-09-01 00:27:27 -0700157 self._db = db.db()
Fang Deng5c508332014-03-19 10:26:00 -0700158 fields = ['status', 'test_name', 'subdir', 'reason',
159 'test_started_time', 'test_finished_time', 'afe_job_id',
160 'job_owner', 'hostname', 'job_tag']
Scott Zawalski63470dd2012-09-05 00:49:43 -0400161 table = 'tko_test_view_2'
162 where = 'job_tag like "%s-%%"' % job_id
163 test_status = []
164 # Run commit before we query to ensure that we are pulling the latest
165 # results.
166 self._db.commit()
167 for entry in self._db.select(','.join(fields), table, (where, None)):
168 status_dict = {}
169 for key,value in zip(fields, entry):
170 # All callers expect values to be a str object.
171 status_dict[key] = str(value)
172 # id is used by TestStatus to uniquely identify each Test Status
173 # obj.
174 status_dict['id'] = [status_dict['reason'], status_dict['hostname'],
175 status_dict['test_name']]
176 test_status.append(status_dict)
177
178 return [TestStatus(self, e) for e in test_status]
mblighc31e4022008-12-11 19:32:30 +0000179
180
181 def get_status_counts(self, job, **data):
182 entries = self.run('get_status_counts',
mbligh1ef218d2009-08-03 16:57:56 +0000183 group_by=['hostname', 'test_name', 'reason'],
mblighc31e4022008-12-11 19:32:30 +0000184 job_tag__startswith='%s-' % job, **data)
mbligh5280e3b2008-12-22 14:39:28 +0000185 return [TestStatus(self, e) for e in entries['groups']]
mblighc31e4022008-12-11 19:32:30 +0000186
187
Richard Barnette260cbd02016-10-06 12:23:28 -0700188class _StableVersionMap(object):
189 """
190 A mapping from board names to strings naming software versions.
191
192 The mapping is meant to allow finding a nominally "stable" version
193 of software associated with a given board. The mapping identifies
194 specific versions of software that should be installed during
195 operations such as repair.
196
197 Conceptually, there are multiple version maps, each handling
198 different types of image. For instance, a single board may have
199 both a stable OS image (e.g. for CrOS), and a separate stable
200 firmware image.
201
202 Each different type of image requires a certain amount of special
203 handling, implemented by a subclass of `StableVersionMap`. The
204 subclasses take care of pre-processing of arguments, delegating
205 actual RPC calls to this superclass.
206
207 @property _afe AFE object through which to make the actual RPC
208 calls.
209 @property _android Value of the `android` parameter to be passed
210 when calling the `get_stable_version` RPC.
211 """
212
213 # DEFAULT_BOARD - The stable_version RPC API recognizes this special
214 # name as a mapping to use when no specific mapping for a board is
215 # present. This default mapping is only allowed for CrOS image
216 # types; other image type subclasses exclude it.
217 #
218 # TODO(jrbarnette): This value is copied from
219 # site_utils.stable_version_utils, because if we import that
220 # module here, it breaks unit tests. Something about the Django
221 # setup...
222 DEFAULT_BOARD = 'DEFAULT'
223
224
225 def __init__(self, afe, android):
226 self._afe = afe
227 self._android = android
228
229
230 def get_all_versions(self):
231 """
232 Get all mappings in the stable versions table.
233
234 Extracts the full content of the `stable_version` table
235 in the AFE database, and returns it as a dictionary
236 mapping board names to version strings.
237
238 @return A dictionary mapping board names to version strings.
239 """
240 return self._afe.run('get_all_stable_versions')
241
242
243 def get_version(self, board):
244 """
245 Get the mapping of one board in the stable versions table.
246
247 Look up and return the version mapped to the given board in the
248 `stable_versions` table in the AFE database.
249
250 @param board The board to be looked up.
251
252 @return The version mapped for the given board.
253 """
254 return self._afe.run('get_stable_version',
255 board=board, android=self._android)
256
257
258 def set_version(self, board, version):
259 """
260 Change the mapping of one board in the stable versions table.
261
262 Set the mapping in the `stable_versions` table in the AFE
263 database for the given board to the given version.
264
265 @param board The board to be updated.
266 @param version The new version to be assigned to the board.
267 """
268 self._afe.run('set_stable_version',
269 version=version, board=board)
270
271
272 def delete_version(self, board):
273 """
274 Remove the mapping of one board in the stable versions table.
275
276 Remove the mapping in the `stable_versions` table in the AFE
277 database for the given board.
278
279 @param board The board to be updated.
280 """
281 self._afe.run('delete_stable_version', board=board)
282
283
284class _OSVersionMap(_StableVersionMap):
285 """
286 Abstract stable version mapping for full OS images of various types.
287 """
288
289 def get_all_versions(self):
Richard Barnettee50453e2016-10-10 16:43:44 -0700290 # TODO(jrbarnette): We exclude non-OS (i.e. firmware) version
291 # mappings, but the returned dict doesn't distinguish CrOS
292 # boards from Android boards; both will be present, and the
293 # subclass can't distinguish them.
Richard Barnette260cbd02016-10-06 12:23:28 -0700294 #
295 # Ultimately, the right fix is to move knowledge of image type
296 # over to the RPC server side.
297 #
298 versions = super(_OSVersionMap, self).get_all_versions()
299 for board in versions.keys():
300 if '/' in board:
301 del versions[board]
302 return versions
303
304
305class _CrosVersionMap(_OSVersionMap):
306 """
307 Stable version mapping for Chrome OS release images.
308
309 This class manages a mapping of Chrome OS board names to known-good
310 release (or canary) images. The images selected can be installed on
311 DUTs during repair tasks, as a way of getting a DUT into a known
312 working state.
313 """
314
315 def __init__(self, afe):
316 super(_CrosVersionMap, self).__init__(afe, False)
317
Richard Barnette728e36f2016-11-03 16:04:29 -0700318 @staticmethod
319 def format_image_name(board, version):
320 """
321 Return an image name for a given `board` and `version`.
322
323 This formats `board` and `version` into a string identifying an
324 image file. The string represents part of a URL for access to
325 the image.
326
327 The returned image name is typically of a form like
328 "falco-release/R55-8872.44.0".
329 """
330 build_pattern = GLOBAL_CONFIG.get_config_value(
331 'CROS', 'stable_build_pattern')
332 return build_pattern % (board, version)
Richard Barnette260cbd02016-10-06 12:23:28 -0700333
Richard Barnette383ef9c2016-12-13 11:56:49 -0800334 def get_image_name(self, board):
335 """
336 Return the full image name of the stable version for `board`.
337
338 This finds the stable version for `board`, and returns a string
Richard Barnette728e36f2016-11-03 16:04:29 -0700339 identifying the associated image as for `format_image_name()`,
340 above.
Richard Barnette383ef9c2016-12-13 11:56:49 -0800341
342 @return A string identifying the image file for the stable
343 image for `board`.
344 """
Richard Barnette728e36f2016-11-03 16:04:29 -0700345 return self.format_image_name(board, self.get_version(board))
Richard Barnette260cbd02016-10-06 12:23:28 -0700346
347
348class _AndroidVersionMap(_OSVersionMap):
349 """
350 Stable version mapping for Android release images.
351
352 This class manages a mapping of Android/Brillo board names to
353 known-good images.
354 """
355
356 def __init__(self, afe):
357 super(_AndroidVersionMap, self).__init__(afe, True)
358
359
360 def get_all_versions(self):
361 versions = super(_AndroidVersionMap, self).get_all_versions()
362 del versions[self.DEFAULT_BOARD]
363 return versions
364
365
Richard Barnettee50453e2016-10-10 16:43:44 -0700366class _SuffixHackVersionMap(_StableVersionMap):
Richard Barnette260cbd02016-10-06 12:23:28 -0700367 """
Richard Barnettee50453e2016-10-10 16:43:44 -0700368 Abstract super class for mappings using a pseudo-board name.
Richard Barnette260cbd02016-10-06 12:23:28 -0700369
Richard Barnettee50453e2016-10-10 16:43:44 -0700370 For non-OS image type mappings, we look them up in the
371 `stable_versions` table by constructing a "pseudo-board" from the
372 real board name plus a suffix string that identifies the image type.
373 So, for instance the name "lulu/firmware" is used to look up the
374 FAFT firmware version for lulu boards.
Richard Barnette260cbd02016-10-06 12:23:28 -0700375 """
376
Richard Barnettee50453e2016-10-10 16:43:44 -0700377 # _SUFFIX - The suffix used in constructing the "pseudo-board"
378 # lookup key. Each subclass must define this value for itself.
Richard Barnette260cbd02016-10-06 12:23:28 -0700379 #
Richard Barnettee50453e2016-10-10 16:43:44 -0700380 _SUFFIX = None
Richard Barnette260cbd02016-10-06 12:23:28 -0700381
382 def __init__(self, afe):
Richard Barnettee50453e2016-10-10 16:43:44 -0700383 super(_SuffixHackVersionMap, self).__init__(afe, False)
Richard Barnette260cbd02016-10-06 12:23:28 -0700384
385
386 def get_all_versions(self):
Richard Barnettee50453e2016-10-10 16:43:44 -0700387 # Get all the mappings from the AFE, extract just the mappings
388 # with our suffix, and replace the pseudo-board name keys with
Richard Barnette260cbd02016-10-06 12:23:28 -0700389 # the real board names.
390 #
391 all_versions = super(
Richard Barnettee50453e2016-10-10 16:43:44 -0700392 _SuffixHackVersionMap, self).get_all_versions()
Richard Barnette260cbd02016-10-06 12:23:28 -0700393 return {
394 board[0 : -len(self._SUFFIX)]: all_versions[board]
395 for board in all_versions.keys()
396 if board.endswith(self._SUFFIX)
397 }
398
399
400 def get_version(self, board):
Richard Barnettee50453e2016-10-10 16:43:44 -0700401 board += self._SUFFIX
402 return super(_SuffixHackVersionMap, self).get_version(board)
403
404
405 def set_version(self, board, version):
406 board += self._SUFFIX
407 super(_SuffixHackVersionMap, self).set_version(board, version)
408
409
410 def delete_version(self, board):
411 board += self._SUFFIX
412 super(_SuffixHackVersionMap, self).delete_version(board)
413
414
415class _FAFTVersionMap(_SuffixHackVersionMap):
416 """
417 Stable version mapping for firmware versions used in FAFT repair.
418
419 When DUTs used for FAFT fail repair, stable firmware may need to be
420 flashed directly from original tarballs. The FAFT firmware version
421 mapping finds the appropriate tarball for a given board.
422 """
423
424 _SUFFIX = '/firmware'
425
426 def get_version(self, board):
427 # If there's no mapping for `board`, the lookup will return the
428 # default CrOS version mapping. To eliminate that case, we
429 # require a '/' character in the version, since CrOS versions
430 # won't match that.
Richard Barnette260cbd02016-10-06 12:23:28 -0700431 #
432 # TODO(jrbarnette): This is, of course, a hack. Ultimately,
433 # the right fix is to move handling to the RPC server side.
434 #
Richard Barnette260cbd02016-10-06 12:23:28 -0700435 version = super(_FAFTVersionMap, self).get_version(board)
436 return version if '/' in version else None
437
438
Richard Barnettee50453e2016-10-10 16:43:44 -0700439class _FirmwareVersionMap(_SuffixHackVersionMap):
440 """
441 Stable version mapping for firmware supplied in Chrome OS images.
Richard Barnette260cbd02016-10-06 12:23:28 -0700442
Richard Barnettee50453e2016-10-10 16:43:44 -0700443 A Chrome OS image bundles a version of the firmware that the
444 device should update to when the OS version is installed during
445 AU.
Richard Barnette260cbd02016-10-06 12:23:28 -0700446
Richard Barnettee50453e2016-10-10 16:43:44 -0700447 Test images suppress the firmware update during AU. Instead, during
448 repair and verify we check installed firmware on a DUT, compare it
449 against the stable version mapping for the board, and update when
450 the DUT is out-of-date.
451 """
452
453 _SUFFIX = '/rwfw'
454
455 def get_version(self, board):
456 # If there's no mapping for `board`, the lookup will return the
457 # default CrOS version mapping. To eliminate that case, we
458 # require the version start with "Google_", since CrOS versions
459 # won't match that.
460 #
461 # TODO(jrbarnette): This is, of course, a hack. Ultimately,
462 # the right fix is to move handling to the RPC server side.
463 #
464 version = super(_FirmwareVersionMap, self).get_version(board)
465 return version if version.startswith('Google_') else None
Richard Barnette260cbd02016-10-06 12:23:28 -0700466
467
mbligh5280e3b2008-12-22 14:39:28 +0000468class AFE(RpcClient):
mbligh1ef218d2009-08-03 16:57:56 +0000469
Richard Barnette260cbd02016-10-06 12:23:28 -0700470 # Known image types for stable version mapping objects.
471 # CROS_IMAGE_TYPE - Mappings for Chrome OS images.
472 # FAFT_IMAGE_TYPE - Mappings for Firmware images for FAFT repair.
Richard Barnettee50453e2016-10-10 16:43:44 -0700473 # FIRMWARE_IMAGE_TYPE - Mappings for released RW Firmware images.
Richard Barnette260cbd02016-10-06 12:23:28 -0700474 # ANDROID_IMAGE_TYPE - Mappings for Android images.
475 #
476 CROS_IMAGE_TYPE = 'cros'
477 FAFT_IMAGE_TYPE = 'faft'
Richard Barnettee50453e2016-10-10 16:43:44 -0700478 FIRMWARE_IMAGE_TYPE = 'firmware'
Richard Barnette260cbd02016-10-06 12:23:28 -0700479 ANDROID_IMAGE_TYPE = 'android'
480
481 _IMAGE_MAPPING_CLASSES = {
482 CROS_IMAGE_TYPE: _CrosVersionMap,
483 FAFT_IMAGE_TYPE: _FAFTVersionMap,
Richard Barnettee50453e2016-10-10 16:43:44 -0700484 FIRMWARE_IMAGE_TYPE: _FirmwareVersionMap,
Richard Barnette260cbd02016-10-06 12:23:28 -0700485 ANDROID_IMAGE_TYPE: _AndroidVersionMap
486 }
487
488
Prathmesh Prabhu6d5ba592017-01-05 13:56:04 -0800489 def __init__(self, user=None, server=None, print_log=True, debug=False,
490 reply_debug=False, job=None):
491 self.job = job
492 super(AFE, self).__init__(path='/afe/server/noauth/rpc/',
493 user=user,
494 server=server,
495 print_log=print_log,
496 debug=debug,
497 reply_debug=reply_debug)
498
499
Richard Barnette260cbd02016-10-06 12:23:28 -0700500 def get_stable_version_map(self, image_type):
501 """
502 Return a stable version mapping for the given image type.
503
504 @return An object mapping board names to version strings for
505 software of the given image type.
506 """
507 return self._IMAGE_MAPPING_CLASSES[image_type](self)
508
509
mbligh67647152008-11-19 00:18:14 +0000510 def host_statuses(self, live=None):
jamesren121eee62010-04-13 19:10:12 +0000511 dead_statuses = ['Repair Failed', 'Repairing']
mbligh67647152008-11-19 00:18:14 +0000512 statuses = self.run('get_static_data')['host_statuses']
513 if live == True:
mblighc2847b72009-03-25 19:32:20 +0000514 return list(set(statuses) - set(dead_statuses))
mbligh67647152008-11-19 00:18:14 +0000515 if live == False:
516 return dead_statuses
517 else:
518 return statuses
519
520
mbligh71094012009-12-19 05:35:21 +0000521 @staticmethod
522 def _dict_for_host_query(hostnames=(), status=None, label=None):
523 query_args = {}
mbligh4e545a52009-12-19 05:30:39 +0000524 if hostnames:
525 query_args['hostname__in'] = hostnames
526 if status:
527 query_args['status'] = status
528 if label:
529 query_args['labels__name'] = label
mbligh71094012009-12-19 05:35:21 +0000530 return query_args
531
532
533 def get_hosts(self, hostnames=(), status=None, label=None, **dargs):
534 query_args = dict(dargs)
535 query_args.update(self._dict_for_host_query(hostnames=hostnames,
536 status=status,
537 label=label))
538 hosts = self.run('get_hosts', **query_args)
539 return [Host(self, h) for h in hosts]
540
541
542 def get_hostnames(self, status=None, label=None, **dargs):
543 """Like get_hosts() but returns hostnames instead of Host objects."""
544 # This implementation can be replaced with a more efficient one
545 # that does not query for entire host objects in the future.
546 return [host_obj.hostname for host_obj in
547 self.get_hosts(status=status, label=label, **dargs)]
548
549
550 def reverify_hosts(self, hostnames=(), status=None, label=None):
551 query_args = dict(locked=False,
552 aclgroup__users__login=self.user)
553 query_args.update(self._dict_for_host_query(hostnames=hostnames,
554 status=status,
555 label=label))
mbligh4e545a52009-12-19 05:30:39 +0000556 return self.run('reverify_hosts', **query_args)
557
558
mbligh67647152008-11-19 00:18:14 +0000559 def create_host(self, hostname, **dargs):
mbligh54459c72009-01-21 19:26:44 +0000560 id = self.run('add_host', hostname=hostname, **dargs)
mbligh67647152008-11-19 00:18:14 +0000561 return self.get_hosts(id=id)[0]
562
563
MK Ryuacf35922014-10-03 14:56:49 -0700564 def get_host_attribute(self, attr, **dargs):
565 host_attrs = self.run('get_host_attribute', attribute=attr, **dargs)
566 return [HostAttribute(self, a) for a in host_attrs]
567
568
Chris Masone8abb6fc2012-01-31 09:27:36 -0800569 def set_host_attribute(self, attr, val, **dargs):
570 self.run('set_host_attribute', attribute=attr, value=val, **dargs)
571
572
mbligh67647152008-11-19 00:18:14 +0000573 def get_labels(self, **dargs):
574 labels = self.run('get_labels', **dargs)
mbligh5280e3b2008-12-22 14:39:28 +0000575 return [Label(self, l) for l in labels]
mbligh67647152008-11-19 00:18:14 +0000576
577
578 def create_label(self, name, **dargs):
mbligh54459c72009-01-21 19:26:44 +0000579 id = self.run('add_label', name=name, **dargs)
mbligh67647152008-11-19 00:18:14 +0000580 return self.get_labels(id=id)[0]
581
582
583 def get_acls(self, **dargs):
584 acls = self.run('get_acl_groups', **dargs)
mbligh5280e3b2008-12-22 14:39:28 +0000585 return [Acl(self, a) for a in acls]
mbligh67647152008-11-19 00:18:14 +0000586
587
588 def create_acl(self, name, **dargs):
mbligh54459c72009-01-21 19:26:44 +0000589 id = self.run('add_acl_group', name=name, **dargs)
mbligh67647152008-11-19 00:18:14 +0000590 return self.get_acls(id=id)[0]
591
592
mbligh54459c72009-01-21 19:26:44 +0000593 def get_users(self, **dargs):
594 users = self.run('get_users', **dargs)
595 return [User(self, u) for u in users]
596
597
mbligh1354c9d2008-12-22 14:56:13 +0000598 def generate_control_file(self, tests, **dargs):
599 ret = self.run('generate_control_file', tests=tests, **dargs)
600 return ControlFile(self, ret)
601
602
mbligh67647152008-11-19 00:18:14 +0000603 def get_jobs(self, summary=False, **dargs):
604 if summary:
605 jobs_data = self.run('get_jobs_summary', **dargs)
606 else:
607 jobs_data = self.run('get_jobs', **dargs)
mblighafbba0c2009-06-08 16:44:45 +0000608 jobs = []
609 for j in jobs_data:
610 job = Job(self, j)
611 # Set up some extra information defaults
612 job.testname = re.sub('\s.*', '', job.name) # arbitrary default
613 job.platform_results = {}
614 job.platform_reasons = {}
615 jobs.append(job)
616 return jobs
mbligh67647152008-11-19 00:18:14 +0000617
618
619 def get_host_queue_entries(self, **data):
620 entries = self.run('get_host_queue_entries', **data)
mblighf9e35862009-02-26 01:03:11 +0000621 job_statuses = [JobStatus(self, e) for e in entries]
mbligh99b24f42009-06-08 16:45:55 +0000622
623 # Sadly, get_host_queue_entries doesn't return platforms, we have
624 # to get those back from an explicit get_hosts queury, then patch
625 # the new host objects back into the host list.
626 hostnames = [s.host.hostname for s in job_statuses if s.host]
627 host_hash = {}
628 for host in self.get_hosts(hostname__in=hostnames):
629 host_hash[host.hostname] = host
630 for status in job_statuses:
631 if status.host:
Fang Deng97dafbc2015-04-23 23:06:18 -0700632 status.host = host_hash.get(status.host.hostname)
mblighf9e35862009-02-26 01:03:11 +0000633 # filter job statuses that have either host or meta_host
634 return [status for status in job_statuses if (status.host or
635 status.meta_host)]
mbligh67647152008-11-19 00:18:14 +0000636
637
MK Ryu1b2d7f92015-02-24 17:45:02 -0800638 def get_special_tasks(self, **data):
639 tasks = self.run('get_special_tasks', **data)
640 return [SpecialTask(self, t) for t in tasks]
641
642
J. Richard Barnette9f10c9f2015-04-13 16:44:50 -0700643 def get_host_special_tasks(self, host_id, **data):
644 tasks = self.run('get_host_special_tasks',
645 host_id=host_id, **data)
646 return [SpecialTask(self, t) for t in tasks]
647
648
J. Richard Barnette8dbd6d32015-05-01 11:01:12 -0700649 def get_host_status_task(self, host_id, end_time):
650 task = self.run('get_host_status_task',
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700651 host_id=host_id, end_time=end_time)
J. Richard Barnettebc9a7952015-04-16 17:43:27 -0700652 return SpecialTask(self, task) if task else None
653
654
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700655 def get_host_diagnosis_interval(self, host_id, end_time, success):
656 return self.run('get_host_diagnosis_interval',
657 host_id=host_id, end_time=end_time,
658 success=success)
659
660
Allen Li352b86a2016-12-14 12:11:27 -0800661 def create_job(self, control_file, name=' ',
662 priority=priorities.Priority.DEFAULT,
663 control_type=control_data.CONTROL_TYPE_NAMES.CLIENT,
664 **dargs):
mbligh67647152008-11-19 00:18:14 +0000665 id = self.run('create_job', name=name, priority=priority,
666 control_file=control_file, control_type=control_type, **dargs)
667 return self.get_jobs(id=id)[0]
668
669
Simran Basi01984f52015-10-12 15:36:45 -0700670 def abort_jobs(self, jobs):
671 """Abort a list of jobs.
672
673 Already completed jobs will not be affected.
674
675 @param jobs: List of job ids to abort.
676 """
677 for job in jobs:
678 self.run('abort_host_queue_entries', job_id=job)
679
680
Kevin Cheng19521982016-09-22 12:27:23 -0700681 def get_hosts_by_attribute(self, attribute, value):
682 """
683 Get the list of hosts that share the same host attribute value.
684
685 @param attribute: String of the host attribute to check.
686 @param value: String of the value that is shared between hosts.
687
688 @returns List of hostnames that all have the same host attribute and
689 value.
690 """
691 return self.run('get_hosts_by_attribute',
692 attribute=attribute, value=value)
693
694
695 def lock_host(self, host, lock_reason, fail_if_locked=False):
696 """
697 Lock the given host with the given lock reason.
698
699 Locking a host that's already locked using the 'modify_hosts' rpc
700 will raise an exception. That's why fail_if_locked exists so the
701 caller can determine if the lock succeeded or failed. This will
702 save every caller from wrapping lock_host in a try-except.
703
704 @param host: hostname of host to lock.
705 @param lock_reason: Reason for locking host.
706 @param fail_if_locked: Return False if host is already locked.
707
708 @returns Boolean, True if lock was successful, False otherwise.
709 """
710 try:
711 self.run('modify_hosts',
712 host_filter_data={'hostname': host},
713 update_data={'locked': True,
714 'lock_reason': lock_reason})
715 except Exception:
716 return not fail_if_locked
717 return True
718
719
720 def unlock_hosts(self, locked_hosts):
721 """
722 Unlock the hosts.
723
724 Unlocking a host that's already unlocked will do nothing so we don't
725 need any special try-except clause here.
726
727 @param locked_hosts: List of hostnames of hosts to unlock.
728 """
729 self.run('modify_hosts',
730 host_filter_data={'hostname__in': locked_hosts},
731 update_data={'locked': False,
732 'lock_reason': ''})
733
734
mbligh5280e3b2008-12-22 14:39:28 +0000735class TestResults(object):
736 """
737 Container class used to hold the results of the tests for a job
738 """
739 def __init__(self):
740 self.good = []
741 self.fail = []
mbligh451ede12009-02-12 21:54:03 +0000742 self.pending = []
mbligh5280e3b2008-12-22 14:39:28 +0000743
744
745 def add(self, result):
mbligh451ede12009-02-12 21:54:03 +0000746 if result.complete_count > result.pass_count:
747 self.fail.append(result)
748 elif result.incomplete_count > 0:
749 self.pending.append(result)
mbligh5280e3b2008-12-22 14:39:28 +0000750 else:
mbligh451ede12009-02-12 21:54:03 +0000751 self.good.append(result)
mbligh5280e3b2008-12-22 14:39:28 +0000752
753
754class RpcObject(object):
mbligh67647152008-11-19 00:18:14 +0000755 """
756 Generic object used to construct python objects from rpc calls
757 """
758 def __init__(self, afe, hash):
759 self.afe = afe
760 self.hash = hash
761 self.__dict__.update(hash)
762
763
764 def __str__(self):
765 return dump_object(self.__repr__(), self)
766
767
mbligh1354c9d2008-12-22 14:56:13 +0000768class ControlFile(RpcObject):
769 """
770 AFE control file object
771
772 Fields: synch_count, dependencies, control_file, is_server
773 """
774 def __repr__(self):
775 return 'CONTROL FILE: %s' % self.control_file
776
777
mbligh5280e3b2008-12-22 14:39:28 +0000778class Label(RpcObject):
mbligh67647152008-11-19 00:18:14 +0000779 """
780 AFE label object
781
782 Fields:
783 name, invalid, platform, kernel_config, id, only_if_needed
784 """
785 def __repr__(self):
786 return 'LABEL: %s' % self.name
787
788
789 def add_hosts(self, hosts):
Chris Masone3a560bd2011-11-14 16:53:56 -0800790 return self.afe.run('label_add_hosts', id=self.id, hosts=hosts)
mbligh67647152008-11-19 00:18:14 +0000791
792
793 def remove_hosts(self, hosts):
Chris Masone3a560bd2011-11-14 16:53:56 -0800794 return self.afe.run('label_remove_hosts', id=self.id, hosts=hosts)
mbligh67647152008-11-19 00:18:14 +0000795
796
mbligh5280e3b2008-12-22 14:39:28 +0000797class Acl(RpcObject):
mbligh67647152008-11-19 00:18:14 +0000798 """
799 AFE acl object
800
801 Fields:
802 users, hosts, description, name, id
803 """
804 def __repr__(self):
805 return 'ACL: %s' % self.name
806
807
808 def add_hosts(self, hosts):
809 self.afe.log('Adding hosts %s to ACL %s' % (hosts, self.name))
810 return self.afe.run('acl_group_add_hosts', self.id, hosts)
811
812
813 def remove_hosts(self, hosts):
814 self.afe.log('Removing hosts %s from ACL %s' % (hosts, self.name))
815 return self.afe.run('acl_group_remove_hosts', self.id, hosts)
816
817
mbligh54459c72009-01-21 19:26:44 +0000818 def add_users(self, users):
819 self.afe.log('Adding users %s to ACL %s' % (users, self.name))
820 return self.afe.run('acl_group_add_users', id=self.name, users=users)
821
822
mbligh5280e3b2008-12-22 14:39:28 +0000823class Job(RpcObject):
mbligh67647152008-11-19 00:18:14 +0000824 """
825 AFE job object
826
827 Fields:
828 name, control_file, control_type, synch_count, reboot_before,
829 run_verify, priority, email_list, created_on, dependencies,
830 timeout, owner, reboot_after, id
831 """
832 def __repr__(self):
833 return 'JOB: %s' % self.id
834
835
mbligh5280e3b2008-12-22 14:39:28 +0000836class JobStatus(RpcObject):
mbligh67647152008-11-19 00:18:14 +0000837 """
838 AFE job_status object
839
840 Fields:
841 status, complete, deleted, meta_host, host, active, execution_subdir, id
842 """
843 def __init__(self, afe, hash):
MK Ryu1b2d7f92015-02-24 17:45:02 -0800844 super(JobStatus, self).__init__(afe, hash)
mbligh5280e3b2008-12-22 14:39:28 +0000845 self.job = Job(afe, self.job)
Dale Curtis8adf7892011-09-08 16:13:36 -0700846 if getattr(self, 'host'):
mbligh99b24f42009-06-08 16:45:55 +0000847 self.host = Host(afe, self.host)
mbligh67647152008-11-19 00:18:14 +0000848
849
850 def __repr__(self):
mbligh451ede12009-02-12 21:54:03 +0000851 if self.host and self.host.hostname:
852 hostname = self.host.hostname
853 else:
854 hostname = 'None'
855 return 'JOB STATUS: %s-%s' % (self.job.id, hostname)
mbligh67647152008-11-19 00:18:14 +0000856
857
MK Ryu1b2d7f92015-02-24 17:45:02 -0800858class SpecialTask(RpcObject):
859 """
860 AFE special task object
861 """
862 def __init__(self, afe, hash):
863 super(SpecialTask, self).__init__(afe, hash)
864 self.host = Host(afe, self.host)
865
866
867 def __repr__(self):
868 return 'SPECIAL TASK: %s' % self.id
869
870
mbligh5280e3b2008-12-22 14:39:28 +0000871class Host(RpcObject):
mbligh67647152008-11-19 00:18:14 +0000872 """
873 AFE host object
874
875 Fields:
876 status, lock_time, locked_by, locked, hostname, invalid,
Allen Lie4c08272017-02-01 16:40:53 -0800877 labels, platform, protection, dirty, id
mbligh67647152008-11-19 00:18:14 +0000878 """
879 def __repr__(self):
880 return 'HOST OBJECT: %s' % self.hostname
881
882
883 def show(self):
884 labels = list(set(self.labels) - set([self.platform]))
885 print '%-6s %-7s %-7s %-16s %s' % (self.hostname, self.status,
886 self.locked, self.platform,
887 ', '.join(labels))
888
889
mbligh54459c72009-01-21 19:26:44 +0000890 def delete(self):
891 return self.afe.run('delete_host', id=self.id)
892
893
mbligh6463c4b2009-01-30 00:33:37 +0000894 def modify(self, **dargs):
895 return self.afe.run('modify_host', id=self.id, **dargs)
896
897
mbligh67647152008-11-19 00:18:14 +0000898 def get_acls(self):
899 return self.afe.get_acls(hosts__hostname=self.hostname)
900
901
902 def add_acl(self, acl_name):
903 self.afe.log('Adding ACL %s to host %s' % (acl_name, self.hostname))
904 return self.afe.run('acl_group_add_hosts', id=acl_name,
905 hosts=[self.hostname])
906
907
908 def remove_acl(self, acl_name):
909 self.afe.log('Removing ACL %s from host %s' % (acl_name, self.hostname))
910 return self.afe.run('acl_group_remove_hosts', id=acl_name,
911 hosts=[self.hostname])
912
913
914 def get_labels(self):
915 return self.afe.get_labels(host__hostname__in=[self.hostname])
916
917
918 def add_labels(self, labels):
919 self.afe.log('Adding labels %s to host %s' % (labels, self.hostname))
920 return self.afe.run('host_add_labels', id=self.id, labels=labels)
921
922
923 def remove_labels(self, labels):
924 self.afe.log('Removing labels %s from host %s' % (labels,self.hostname))
925 return self.afe.run('host_remove_labels', id=self.id, labels=labels)
mbligh5b618382008-12-03 15:24:01 +0000926
927
mbligh54459c72009-01-21 19:26:44 +0000928class User(RpcObject):
929 def __repr__(self):
930 return 'USER: %s' % self.login
931
932
mbligh5280e3b2008-12-22 14:39:28 +0000933class TestStatus(RpcObject):
mblighc31e4022008-12-11 19:32:30 +0000934 """
935 TKO test status object
936
937 Fields:
938 test_idx, hostname, testname, id
939 complete_count, incomplete_count, group_count, pass_count
940 """
941 def __repr__(self):
942 return 'TEST STATUS: %s' % self.id
943
944
MK Ryuacf35922014-10-03 14:56:49 -0700945class HostAttribute(RpcObject):
946 """
947 AFE host attribute object
948
949 Fields:
950 id, host, attribute, value
951 """
952 def __repr__(self):
953 return 'HOST ATTRIBUTE %d' % self.id