blob: 71f661ffade14c30809f058f717913aff82c3843 [file] [log] [blame]
Derek Beckett63e1c442020-08-11 14:49:47 -07001# Lint as: python2, python3
mbligh67647152008-11-19 00:18:14 +00002# Copyright Martin J. Bligh, Google Inc 2008
3# Released under the GPL v2
4
5"""
6This class allows you to communicate with the frontend to submit jobs etc
7It is designed for writing more sophisiticated server-side control files that
8can recursively add and manage other jobs.
9
10We turn the JSON dictionaries into real objects that are more idiomatic
11
mblighc31e4022008-12-11 19:32:30 +000012For docs, see:
Aviv Keshet2c709f62013-05-07 12:52:15 -070013 http://www.chromium.org/chromium-os/testing/afe-rpc-infrastructure
mblighc31e4022008-12-11 19:32:30 +000014 http://docs.djangoproject.com/en/dev/ref/models/querysets/#queryset-api
mbligh67647152008-11-19 00:18:14 +000015"""
16
Richard Barnetteb9b37982016-05-20 19:23:39 -070017#pylint: disable=missing-docstring
18
Derek Beckett63e1c442020-08-11 14:49:47 -070019from __future__ import absolute_import
20from __future__ import division
21from __future__ import print_function
22
Dan Shie8e0c052015-09-01 00:27:27 -070023import getpass
24import os
25import re
Dan Shie8e0c052015-09-01 00:27:27 -070026
mbligh67647152008-11-19 00:18:14 +000027import common
Allen Li327e6fd2016-11-22 13:45:41 -080028
mbligh67647152008-11-19 00:18:14 +000029from autotest_lib.frontend.afe import rpc_client_lib
Dan Shie8e0c052015-09-01 00:27:27 -070030from autotest_lib.client.common_lib import control_data
mbligh37eceaa2008-12-15 22:56:37 +000031from autotest_lib.client.common_lib import global_config
Allen Lif74957d2017-11-20 17:46:48 -080032from autotest_lib.client.common_lib import host_states
Allen Li352b86a2016-12-14 12:11:27 -080033from autotest_lib.client.common_lib import priorities
mbligh67647152008-11-19 00:18:14 +000034from autotest_lib.client.common_lib import utils
Scott Zawalski63470dd2012-09-05 00:49:43 -040035from autotest_lib.tko import db
Derek Beckett63e1c442020-08-11 14:49:47 -070036from six.moves import zip
Scott Zawalski63470dd2012-09-05 00:49:43 -040037
Dan Shi5e2efb72017-02-07 11:40:23 -080038try:
Mike Frysinger714c5b02020-09-04 23:22:54 -040039 from autotest_lib.utils.frozen_chromite.lib import metrics
Dan Shi5e2efb72017-02-07 11:40:23 -080040except ImportError:
41 metrics = utils.metrics_mock
Scott Zawalski63470dd2012-09-05 00:49:43 -040042
mbligh4e576612008-12-22 14:56:36 +000043try:
44 from autotest_lib.server.site_common import site_utils as server_utils
45except:
46 from autotest_lib.server import utils as server_utils
47form_ntuples_from_machines = server_utils.form_ntuples_from_machines
mbligh67647152008-11-19 00:18:14 +000048
mbligh37eceaa2008-12-15 22:56:37 +000049GLOBAL_CONFIG = global_config.global_config
50DEFAULT_SERVER = 'autotest'
51
Dan Shie8e0c052015-09-01 00:27:27 -070052
mbligh67647152008-11-19 00:18:14 +000053def dump_object(header, obj):
54 """
55 Standard way to print out the frontend objects (eg job, host, acl, label)
56 in a human-readable fashion for debugging
57 """
58 result = header + '\n'
59 for key in obj.hash:
60 if key == 'afe' or key == 'hash':
61 continue
62 result += '%20s: %s\n' % (key, obj.hash[key])
63 return result
64
65
mbligh5280e3b2008-12-22 14:39:28 +000066class RpcClient(object):
mbligh67647152008-11-19 00:18:14 +000067 """
mbligh451ede12009-02-12 21:54:03 +000068 Abstract RPC class for communicating with the autotest frontend
69 Inherited for both TKO and AFE uses.
mbligh67647152008-11-19 00:18:14 +000070
mbligh1ef218d2009-08-03 16:57:56 +000071 All the constructors go in the afe / tko class.
mbligh451ede12009-02-12 21:54:03 +000072 Manipulating methods go in the object classes themselves
mbligh67647152008-11-19 00:18:14 +000073 """
mbligh99b24f42009-06-08 16:45:55 +000074 def __init__(self, path, user, server, print_log, debug, reply_debug):
mbligh67647152008-11-19 00:18:14 +000075 """
mbligh451ede12009-02-12 21:54:03 +000076 Create a cached instance of a connection to the frontend
mbligh67647152008-11-19 00:18:14 +000077
78 user: username to connect as
mbligh451ede12009-02-12 21:54:03 +000079 server: frontend server to connect to
mbligh67647152008-11-19 00:18:14 +000080 print_log: pring a logging message to stdout on every operation
81 debug: print out all RPC traffic
82 """
Dan Shiff78f112015-06-12 13:34:02 -070083 if not user and utils.is_in_container():
84 user = GLOBAL_CONFIG.get_config_value('SSP', 'user', default=None)
mblighc31e4022008-12-11 19:32:30 +000085 if not user:
mblighdb59e3c2009-11-21 01:45:18 +000086 user = getpass.getuser()
mbligh451ede12009-02-12 21:54:03 +000087 if not server:
mbligh475f7762009-01-30 00:34:04 +000088 if 'AUTOTEST_WEB' in os.environ:
mbligh451ede12009-02-12 21:54:03 +000089 server = os.environ['AUTOTEST_WEB']
mbligh475f7762009-01-30 00:34:04 +000090 else:
mbligh451ede12009-02-12 21:54:03 +000091 server = GLOBAL_CONFIG.get_config_value('SERVER', 'hostname',
92 default=DEFAULT_SERVER)
93 self.server = server
mbligh67647152008-11-19 00:18:14 +000094 self.user = user
95 self.print_log = print_log
96 self.debug = debug
mbligh99b24f42009-06-08 16:45:55 +000097 self.reply_debug = reply_debug
Scott Zawalski347aaf42012-04-03 16:33:00 -040098 headers = {'AUTHORIZATION': self.user}
Prathmesh Prabhu2892ff62017-12-19 10:21:31 -080099 rpc_server = rpc_client_lib.add_protocol(server) + path
mbligh1354c9d2008-12-22 14:56:13 +0000100 if debug:
Derek Beckett63e1c442020-08-11 14:49:47 -0700101 print('SERVER: %s' % rpc_server)
102 print('HEADERS: %s' % headers)
mbligh67647152008-11-19 00:18:14 +0000103 self.proxy = rpc_client_lib.get_proxy(rpc_server, headers=headers)
104
105
106 def run(self, call, **dargs):
107 """
108 Make a RPC call to the AFE server
109 """
110 rpc_call = getattr(self.proxy, call)
111 if self.debug:
Derek Beckett63e1c442020-08-11 14:49:47 -0700112 print('DEBUG: %s %s' % (call, dargs))
mbligh451ede12009-02-12 21:54:03 +0000113 try:
mbligh99b24f42009-06-08 16:45:55 +0000114 result = utils.strip_unicode(rpc_call(**dargs))
115 if self.reply_debug:
Derek Beckett63e1c442020-08-11 14:49:47 -0700116 print(result)
mbligh99b24f42009-06-08 16:45:55 +0000117 return result
mbligh451ede12009-02-12 21:54:03 +0000118 except Exception:
mbligh451ede12009-02-12 21:54:03 +0000119 raise
mbligh67647152008-11-19 00:18:14 +0000120
121
122 def log(self, message):
123 if self.print_log:
Derek Beckett63e1c442020-08-11 14:49:47 -0700124 print(message)
mbligh67647152008-11-19 00:18:14 +0000125
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
Allen Li327e6fd2016-11-22 13:45:41 -0800139 @metrics.SecondsTimerDecorator(
Prathmesh Prabhua7556a92017-02-01 14:18:41 -0800140 'chromeos/autotest/tko/get_job_status_duration')
Scott Zawalski63470dd2012-09-05 00:49:43 -0400141 def get_job_test_statuses_from_db(self, job_id):
142 """Get job test statuses from the database.
143
144 Retrieve a set of fields from a job that reflect the status of each test
145 run within a job.
146 fields retrieved: status, test_name, reason, test_started_time,
147 test_finished_time, afe_job_id, job_owner, hostname.
148
149 @param job_id: The afe job id to look up.
150 @returns a TestStatus object of the resulting information.
151 """
152 if self._db is None:
Dan Shie8e0c052015-09-01 00:27:27 -0700153 self._db = db.db()
Fang Deng5c508332014-03-19 10:26:00 -0700154 fields = ['status', 'test_name', 'subdir', 'reason',
155 'test_started_time', 'test_finished_time', 'afe_job_id',
156 'job_owner', 'hostname', 'job_tag']
Scott Zawalski63470dd2012-09-05 00:49:43 -0400157 table = 'tko_test_view_2'
158 where = 'job_tag like "%s-%%"' % job_id
159 test_status = []
160 # Run commit before we query to ensure that we are pulling the latest
161 # results.
162 self._db.commit()
163 for entry in self._db.select(','.join(fields), table, (where, None)):
164 status_dict = {}
165 for key,value in zip(fields, entry):
166 # All callers expect values to be a str object.
167 status_dict[key] = str(value)
168 # id is used by TestStatus to uniquely identify each Test Status
169 # obj.
170 status_dict['id'] = [status_dict['reason'], status_dict['hostname'],
171 status_dict['test_name']]
172 test_status.append(status_dict)
173
174 return [TestStatus(self, e) for e in test_status]
mblighc31e4022008-12-11 19:32:30 +0000175
176
177 def get_status_counts(self, job, **data):
178 entries = self.run('get_status_counts',
mbligh1ef218d2009-08-03 16:57:56 +0000179 group_by=['hostname', 'test_name', 'reason'],
mblighc31e4022008-12-11 19:32:30 +0000180 job_tag__startswith='%s-' % job, **data)
mbligh5280e3b2008-12-22 14:39:28 +0000181 return [TestStatus(self, e) for e in entries['groups']]
mblighc31e4022008-12-11 19:32:30 +0000182
183
Richard Barnette260cbd02016-10-06 12:23:28 -0700184class _StableVersionMap(object):
185 """
186 A mapping from board names to strings naming software versions.
187
188 The mapping is meant to allow finding a nominally "stable" version
189 of software associated with a given board. The mapping identifies
190 specific versions of software that should be installed during
191 operations such as repair.
192
193 Conceptually, there are multiple version maps, each handling
194 different types of image. For instance, a single board may have
195 both a stable OS image (e.g. for CrOS), and a separate stable
196 firmware image.
197
198 Each different type of image requires a certain amount of special
199 handling, implemented by a subclass of `StableVersionMap`. The
200 subclasses take care of pre-processing of arguments, delegating
201 actual RPC calls to this superclass.
202
203 @property _afe AFE object through which to make the actual RPC
204 calls.
205 @property _android Value of the `android` parameter to be passed
206 when calling the `get_stable_version` RPC.
207 """
208
Richard Barnette81175432018-07-06 17:21:59 -0700209 def __init__(self, afe):
Richard Barnette260cbd02016-10-06 12:23:28 -0700210 self._afe = afe
Richard Barnette260cbd02016-10-06 12:23:28 -0700211
212
213 def get_all_versions(self):
214 """
215 Get all mappings in the stable versions table.
216
217 Extracts the full content of the `stable_version` table
218 in the AFE database, and returns it as a dictionary
219 mapping board names to version strings.
220
221 @return A dictionary mapping board names to version strings.
222 """
223 return self._afe.run('get_all_stable_versions')
224
225
226 def get_version(self, board):
227 """
228 Get the mapping of one board in the stable versions table.
229
230 Look up and return the version mapped to the given board in the
231 `stable_versions` table in the AFE database.
232
233 @param board The board to be looked up.
234
235 @return The version mapped for the given board.
236 """
Richard Barnette81175432018-07-06 17:21:59 -0700237 return self._afe.run('get_stable_version', board=board)
Richard Barnette260cbd02016-10-06 12:23:28 -0700238
239
240 def set_version(self, board, version):
241 """
242 Change the mapping of one board in the stable versions table.
243
244 Set the mapping in the `stable_versions` table in the AFE
245 database for the given board to the given version.
246
247 @param board The board to be updated.
248 @param version The new version to be assigned to the board.
249 """
Gregory Nisbet7f410662019-11-19 09:04:20 -0800250 raise RuntimeError("server.frontend._StableVersionMap::set_version is intentionally deleted")
Richard Barnette260cbd02016-10-06 12:23:28 -0700251
252
253 def delete_version(self, board):
254 """
255 Remove the mapping of one board in the stable versions table.
256
257 Remove the mapping in the `stable_versions` table in the AFE
258 database for the given board.
259
260 @param board The board to be updated.
261 """
Gregory Nisbet7f410662019-11-19 09:04:20 -0800262 raise RuntimeError("server.frontend._StableVersionMap::delete_version is intentionally deleted")
Richard Barnette260cbd02016-10-06 12:23:28 -0700263
264
265class _OSVersionMap(_StableVersionMap):
266 """
267 Abstract stable version mapping for full OS images of various types.
268 """
269
Richard Barnette206b25f2018-04-03 13:53:22 -0700270 def _version_is_valid(self, version):
271 return True
272
Richard Barnette260cbd02016-10-06 12:23:28 -0700273 def get_all_versions(self):
Richard Barnette260cbd02016-10-06 12:23:28 -0700274 versions = super(_OSVersionMap, self).get_all_versions()
275 for board in versions.keys():
Richard Barnette206b25f2018-04-03 13:53:22 -0700276 if ('/' in board
277 or not self._version_is_valid(versions[board])):
Richard Barnette260cbd02016-10-06 12:23:28 -0700278 del versions[board]
279 return versions
280
Richard Barnette206b25f2018-04-03 13:53:22 -0700281 def get_version(self, board):
282 version = super(_OSVersionMap, self).get_version(board)
283 return version if self._version_is_valid(version) else None
284
Richard Barnette260cbd02016-10-06 12:23:28 -0700285
Richard Barnette08e487d2018-03-30 18:39:31 -0700286def format_cros_image_name(board, version):
287 """
288 Return an image name for a given `board` and `version`.
289
290 This formats `board` and `version` into a string identifying an
291 image file. The string represents part of a URL for access to
292 the image.
293
294 The returned image name is typically of a form like
295 "falco-release/R55-8872.44.0".
296 """
297 build_pattern = GLOBAL_CONFIG.get_config_value(
298 'CROS', 'stable_build_pattern')
299 return build_pattern % (board, version)
300
301
Richard Barnette260cbd02016-10-06 12:23:28 -0700302class _CrosVersionMap(_OSVersionMap):
303 """
304 Stable version mapping for Chrome OS release images.
305
306 This class manages a mapping of Chrome OS board names to known-good
307 release (or canary) images. The images selected can be installed on
308 DUTs during repair tasks, as a way of getting a DUT into a known
309 working state.
310 """
311
Richard Barnette206b25f2018-04-03 13:53:22 -0700312 def _version_is_valid(self, version):
313 return version is not None and '/' not in version
314
Richard Barnette383ef9c2016-12-13 11:56:49 -0800315 def get_image_name(self, board):
316 """
317 Return the full image name of the stable version for `board`.
318
319 This finds the stable version for `board`, and returns a string
Richard Barnette728e36f2016-11-03 16:04:29 -0700320 identifying the associated image as for `format_image_name()`,
321 above.
Richard Barnette383ef9c2016-12-13 11:56:49 -0800322
323 @return A string identifying the image file for the stable
324 image for `board`.
325 """
Richard Barnette08e487d2018-03-30 18:39:31 -0700326 return format_cros_image_name(board, self.get_version(board))
Richard Barnette260cbd02016-10-06 12:23:28 -0700327
328
Richard Barnettee50453e2016-10-10 16:43:44 -0700329class _SuffixHackVersionMap(_StableVersionMap):
Richard Barnette260cbd02016-10-06 12:23:28 -0700330 """
Richard Barnettee50453e2016-10-10 16:43:44 -0700331 Abstract super class for mappings using a pseudo-board name.
Richard Barnette260cbd02016-10-06 12:23:28 -0700332
Richard Barnettee50453e2016-10-10 16:43:44 -0700333 For non-OS image type mappings, we look them up in the
334 `stable_versions` table by constructing a "pseudo-board" from the
335 real board name plus a suffix string that identifies the image type.
336 So, for instance the name "lulu/firmware" is used to look up the
337 FAFT firmware version for lulu boards.
Richard Barnette260cbd02016-10-06 12:23:28 -0700338 """
339
Richard Barnettee50453e2016-10-10 16:43:44 -0700340 # _SUFFIX - The suffix used in constructing the "pseudo-board"
341 # lookup key. Each subclass must define this value for itself.
Richard Barnette260cbd02016-10-06 12:23:28 -0700342 #
Richard Barnettee50453e2016-10-10 16:43:44 -0700343 _SUFFIX = None
Richard Barnette260cbd02016-10-06 12:23:28 -0700344
Richard Barnette260cbd02016-10-06 12:23:28 -0700345 def get_all_versions(self):
Richard Barnettee50453e2016-10-10 16:43:44 -0700346 # Get all the mappings from the AFE, extract just the mappings
347 # with our suffix, and replace the pseudo-board name keys with
Richard Barnette260cbd02016-10-06 12:23:28 -0700348 # the real board names.
349 #
350 all_versions = super(
Richard Barnettee50453e2016-10-10 16:43:44 -0700351 _SuffixHackVersionMap, self).get_all_versions()
Richard Barnette260cbd02016-10-06 12:23:28 -0700352 return {
353 board[0 : -len(self._SUFFIX)]: all_versions[board]
354 for board in all_versions.keys()
355 if board.endswith(self._SUFFIX)
356 }
357
358
359 def get_version(self, board):
Richard Barnettee50453e2016-10-10 16:43:44 -0700360 board += self._SUFFIX
361 return super(_SuffixHackVersionMap, self).get_version(board)
362
363
364 def set_version(self, board, version):
365 board += self._SUFFIX
366 super(_SuffixHackVersionMap, self).set_version(board, version)
367
368
369 def delete_version(self, board):
370 board += self._SUFFIX
371 super(_SuffixHackVersionMap, self).delete_version(board)
372
373
374class _FAFTVersionMap(_SuffixHackVersionMap):
375 """
376 Stable version mapping for firmware versions used in FAFT repair.
377
378 When DUTs used for FAFT fail repair, stable firmware may need to be
379 flashed directly from original tarballs. The FAFT firmware version
380 mapping finds the appropriate tarball for a given board.
381 """
382
383 _SUFFIX = '/firmware'
384
385 def get_version(self, board):
386 # If there's no mapping for `board`, the lookup will return the
387 # default CrOS version mapping. To eliminate that case, we
388 # require a '/' character in the version, since CrOS versions
389 # won't match that.
Richard Barnette260cbd02016-10-06 12:23:28 -0700390 #
391 # TODO(jrbarnette): This is, of course, a hack. Ultimately,
392 # the right fix is to move handling to the RPC server side.
393 #
Richard Barnette260cbd02016-10-06 12:23:28 -0700394 version = super(_FAFTVersionMap, self).get_version(board)
395 return version if '/' in version else None
396
397
Richard Barnettee50453e2016-10-10 16:43:44 -0700398class _FirmwareVersionMap(_SuffixHackVersionMap):
399 """
400 Stable version mapping for firmware supplied in Chrome OS images.
Richard Barnette260cbd02016-10-06 12:23:28 -0700401
Richard Barnettee50453e2016-10-10 16:43:44 -0700402 A Chrome OS image bundles a version of the firmware that the
403 device should update to when the OS version is installed during
404 AU.
Richard Barnette260cbd02016-10-06 12:23:28 -0700405
Richard Barnettee50453e2016-10-10 16:43:44 -0700406 Test images suppress the firmware update during AU. Instead, during
407 repair and verify we check installed firmware on a DUT, compare it
408 against the stable version mapping for the board, and update when
409 the DUT is out-of-date.
410 """
411
412 _SUFFIX = '/rwfw'
413
414 def get_version(self, board):
415 # If there's no mapping for `board`, the lookup will return the
416 # default CrOS version mapping. To eliminate that case, we
417 # require the version start with "Google_", since CrOS versions
418 # won't match that.
419 #
420 # TODO(jrbarnette): This is, of course, a hack. Ultimately,
421 # the right fix is to move handling to the RPC server side.
422 #
423 version = super(_FirmwareVersionMap, self).get_version(board)
424 return version if version.startswith('Google_') else None
Richard Barnette260cbd02016-10-06 12:23:28 -0700425
426
mbligh5280e3b2008-12-22 14:39:28 +0000427class AFE(RpcClient):
mbligh1ef218d2009-08-03 16:57:56 +0000428
Richard Barnette260cbd02016-10-06 12:23:28 -0700429 # Known image types for stable version mapping objects.
430 # CROS_IMAGE_TYPE - Mappings for Chrome OS images.
431 # FAFT_IMAGE_TYPE - Mappings for Firmware images for FAFT repair.
Richard Barnettee50453e2016-10-10 16:43:44 -0700432 # FIRMWARE_IMAGE_TYPE - Mappings for released RW Firmware images.
Richard Barnette260cbd02016-10-06 12:23:28 -0700433 #
434 CROS_IMAGE_TYPE = 'cros'
435 FAFT_IMAGE_TYPE = 'faft'
Richard Barnettee50453e2016-10-10 16:43:44 -0700436 FIRMWARE_IMAGE_TYPE = 'firmware'
Richard Barnette260cbd02016-10-06 12:23:28 -0700437
438 _IMAGE_MAPPING_CLASSES = {
439 CROS_IMAGE_TYPE: _CrosVersionMap,
440 FAFT_IMAGE_TYPE: _FAFTVersionMap,
Richard Barnettee50453e2016-10-10 16:43:44 -0700441 FIRMWARE_IMAGE_TYPE: _FirmwareVersionMap,
Richard Barnette260cbd02016-10-06 12:23:28 -0700442 }
443
444
Prathmesh Prabhu6d5ba592017-01-05 13:56:04 -0800445 def __init__(self, user=None, server=None, print_log=True, debug=False,
446 reply_debug=False, job=None):
447 self.job = job
448 super(AFE, self).__init__(path='/afe/server/noauth/rpc/',
449 user=user,
450 server=server,
451 print_log=print_log,
452 debug=debug,
453 reply_debug=reply_debug)
454
455
Richard Barnette260cbd02016-10-06 12:23:28 -0700456 def get_stable_version_map(self, image_type):
457 """
458 Return a stable version mapping for the given image type.
459
460 @return An object mapping board names to version strings for
461 software of the given image type.
462 """
463 return self._IMAGE_MAPPING_CLASSES[image_type](self)
464
465
mbligh67647152008-11-19 00:18:14 +0000466 def host_statuses(self, live=None):
jamesren121eee62010-04-13 19:10:12 +0000467 dead_statuses = ['Repair Failed', 'Repairing']
mbligh67647152008-11-19 00:18:14 +0000468 statuses = self.run('get_static_data')['host_statuses']
469 if live == True:
mblighc2847b72009-03-25 19:32:20 +0000470 return list(set(statuses) - set(dead_statuses))
mbligh67647152008-11-19 00:18:14 +0000471 if live == False:
472 return dead_statuses
473 else:
474 return statuses
475
476
mbligh71094012009-12-19 05:35:21 +0000477 @staticmethod
478 def _dict_for_host_query(hostnames=(), status=None, label=None):
479 query_args = {}
mbligh4e545a52009-12-19 05:30:39 +0000480 if hostnames:
481 query_args['hostname__in'] = hostnames
482 if status:
483 query_args['status'] = status
484 if label:
485 query_args['labels__name'] = label
mbligh71094012009-12-19 05:35:21 +0000486 return query_args
487
488
489 def get_hosts(self, hostnames=(), status=None, label=None, **dargs):
490 query_args = dict(dargs)
491 query_args.update(self._dict_for_host_query(hostnames=hostnames,
492 status=status,
493 label=label))
494 hosts = self.run('get_hosts', **query_args)
495 return [Host(self, h) for h in hosts]
496
497
498 def get_hostnames(self, status=None, label=None, **dargs):
499 """Like get_hosts() but returns hostnames instead of Host objects."""
500 # This implementation can be replaced with a more efficient one
501 # that does not query for entire host objects in the future.
502 return [host_obj.hostname for host_obj in
503 self.get_hosts(status=status, label=label, **dargs)]
504
505
506 def reverify_hosts(self, hostnames=(), status=None, label=None):
507 query_args = dict(locked=False,
508 aclgroup__users__login=self.user)
509 query_args.update(self._dict_for_host_query(hostnames=hostnames,
510 status=status,
511 label=label))
mbligh4e545a52009-12-19 05:30:39 +0000512 return self.run('reverify_hosts', **query_args)
513
514
Richard Barnette73e74b02017-08-10 15:16:49 -0700515 def repair_hosts(self, hostnames=(), status=None, label=None):
516 query_args = dict(locked=False,
517 aclgroup__users__login=self.user)
518 query_args.update(self._dict_for_host_query(hostnames=hostnames,
519 status=status,
520 label=label))
521 return self.run('repair_hosts', **query_args)
522
523
mbligh67647152008-11-19 00:18:14 +0000524 def create_host(self, hostname, **dargs):
mbligh54459c72009-01-21 19:26:44 +0000525 id = self.run('add_host', hostname=hostname, **dargs)
mbligh67647152008-11-19 00:18:14 +0000526 return self.get_hosts(id=id)[0]
527
528
MK Ryuacf35922014-10-03 14:56:49 -0700529 def get_host_attribute(self, attr, **dargs):
530 host_attrs = self.run('get_host_attribute', attribute=attr, **dargs)
531 return [HostAttribute(self, a) for a in host_attrs]
532
533
Chris Masone8abb6fc2012-01-31 09:27:36 -0800534 def set_host_attribute(self, attr, val, **dargs):
535 self.run('set_host_attribute', attribute=attr, value=val, **dargs)
536
537
mbligh67647152008-11-19 00:18:14 +0000538 def get_labels(self, **dargs):
539 labels = self.run('get_labels', **dargs)
mbligh5280e3b2008-12-22 14:39:28 +0000540 return [Label(self, l) for l in labels]
mbligh67647152008-11-19 00:18:14 +0000541
542
543 def create_label(self, name, **dargs):
mbligh54459c72009-01-21 19:26:44 +0000544 id = self.run('add_label', name=name, **dargs)
mbligh67647152008-11-19 00:18:14 +0000545 return self.get_labels(id=id)[0]
546
547
548 def get_acls(self, **dargs):
549 acls = self.run('get_acl_groups', **dargs)
mbligh5280e3b2008-12-22 14:39:28 +0000550 return [Acl(self, a) for a in acls]
mbligh67647152008-11-19 00:18:14 +0000551
552
553 def create_acl(self, name, **dargs):
mbligh54459c72009-01-21 19:26:44 +0000554 id = self.run('add_acl_group', name=name, **dargs)
mbligh67647152008-11-19 00:18:14 +0000555 return self.get_acls(id=id)[0]
556
557
mbligh54459c72009-01-21 19:26:44 +0000558 def get_users(self, **dargs):
559 users = self.run('get_users', **dargs)
560 return [User(self, u) for u in users]
561
562
mbligh1354c9d2008-12-22 14:56:13 +0000563 def generate_control_file(self, tests, **dargs):
564 ret = self.run('generate_control_file', tests=tests, **dargs)
565 return ControlFile(self, ret)
566
567
mbligh67647152008-11-19 00:18:14 +0000568 def get_jobs(self, summary=False, **dargs):
569 if summary:
570 jobs_data = self.run('get_jobs_summary', **dargs)
571 else:
572 jobs_data = self.run('get_jobs', **dargs)
mblighafbba0c2009-06-08 16:44:45 +0000573 jobs = []
574 for j in jobs_data:
575 job = Job(self, j)
576 # Set up some extra information defaults
577 job.testname = re.sub('\s.*', '', job.name) # arbitrary default
578 job.platform_results = {}
579 job.platform_reasons = {}
580 jobs.append(job)
581 return jobs
mbligh67647152008-11-19 00:18:14 +0000582
583
Paul Hobbsf6fbe2d2017-06-07 14:17:07 -0700584 def get_host_queue_entries(self, **kwargs):
585 """Find JobStatus objects matching some constraints.
mbligh99b24f42009-06-08 16:45:55 +0000586
Paul Hobbsf6fbe2d2017-06-07 14:17:07 -0700587 @param **kwargs: Arguments to pass to the RPC
588 """
589 entries = self.run('get_host_queue_entries', **kwargs)
590 return self._entries_to_statuses(entries)
591
592
593 def get_host_queue_entries_by_insert_time(self, **kwargs):
594 """Like get_host_queue_entries, but using the insert index table.
595
596 @param **kwargs: Arguments to pass to the RPC
597 """
598 entries = self.run('get_host_queue_entries_by_insert_time', **kwargs)
599 return self._entries_to_statuses(entries)
600
601
602 def _entries_to_statuses(self, entries):
603 """Converts HQEs to JobStatuses
604
605 Sadly, get_host_queue_entries doesn't return platforms, we have
606 to get those back from an explicit get_hosts queury, then patch
607 the new host objects back into the host list.
608
609 :param entries: A list of HQEs from get_host_queue_entries or
610 get_host_queue_entries_by_insert_time.
611 """
612 job_statuses = [JobStatus(self, e) for e in entries]
mbligh99b24f42009-06-08 16:45:55 +0000613 hostnames = [s.host.hostname for s in job_statuses if s.host]
Paul Hobbsf6fbe2d2017-06-07 14:17:07 -0700614 hosts = {}
mbligh99b24f42009-06-08 16:45:55 +0000615 for host in self.get_hosts(hostname__in=hostnames):
Paul Hobbsf6fbe2d2017-06-07 14:17:07 -0700616 hosts[host.hostname] = host
mbligh99b24f42009-06-08 16:45:55 +0000617 for status in job_statuses:
618 if status.host:
Paul Hobbsf6fbe2d2017-06-07 14:17:07 -0700619 status.host = hosts.get(status.host.hostname)
mblighf9e35862009-02-26 01:03:11 +0000620 # filter job statuses that have either host or meta_host
621 return [status for status in job_statuses if (status.host or
622 status.meta_host)]
mbligh67647152008-11-19 00:18:14 +0000623
624
MK Ryu1b2d7f92015-02-24 17:45:02 -0800625 def get_special_tasks(self, **data):
626 tasks = self.run('get_special_tasks', **data)
627 return [SpecialTask(self, t) for t in tasks]
628
629
J. Richard Barnette9f10c9f2015-04-13 16:44:50 -0700630 def get_host_special_tasks(self, host_id, **data):
631 tasks = self.run('get_host_special_tasks',
632 host_id=host_id, **data)
633 return [SpecialTask(self, t) for t in tasks]
634
635
J. Richard Barnette8dbd6d32015-05-01 11:01:12 -0700636 def get_host_status_task(self, host_id, end_time):
637 task = self.run('get_host_status_task',
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700638 host_id=host_id, end_time=end_time)
J. Richard Barnettebc9a7952015-04-16 17:43:27 -0700639 return SpecialTask(self, task) if task else None
640
641
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700642 def get_host_diagnosis_interval(self, host_id, end_time, success):
643 return self.run('get_host_diagnosis_interval',
644 host_id=host_id, end_time=end_time,
645 success=success)
646
647
Allen Li352b86a2016-12-14 12:11:27 -0800648 def create_job(self, control_file, name=' ',
649 priority=priorities.Priority.DEFAULT,
650 control_type=control_data.CONTROL_TYPE_NAMES.CLIENT,
651 **dargs):
mbligh67647152008-11-19 00:18:14 +0000652 id = self.run('create_job', name=name, priority=priority,
653 control_file=control_file, control_type=control_type, **dargs)
654 return self.get_jobs(id=id)[0]
655
656
Simran Basi01984f52015-10-12 15:36:45 -0700657 def abort_jobs(self, jobs):
658 """Abort a list of jobs.
659
660 Already completed jobs will not be affected.
661
662 @param jobs: List of job ids to abort.
663 """
664 for job in jobs:
665 self.run('abort_host_queue_entries', job_id=job)
666
667
Kevin Cheng19521982016-09-22 12:27:23 -0700668 def get_hosts_by_attribute(self, attribute, value):
669 """
670 Get the list of hosts that share the same host attribute value.
671
672 @param attribute: String of the host attribute to check.
673 @param value: String of the value that is shared between hosts.
674
675 @returns List of hostnames that all have the same host attribute and
676 value.
677 """
678 return self.run('get_hosts_by_attribute',
679 attribute=attribute, value=value)
680
681
682 def lock_host(self, host, lock_reason, fail_if_locked=False):
683 """
684 Lock the given host with the given lock reason.
685
686 Locking a host that's already locked using the 'modify_hosts' rpc
687 will raise an exception. That's why fail_if_locked exists so the
688 caller can determine if the lock succeeded or failed. This will
689 save every caller from wrapping lock_host in a try-except.
690
691 @param host: hostname of host to lock.
692 @param lock_reason: Reason for locking host.
693 @param fail_if_locked: Return False if host is already locked.
694
695 @returns Boolean, True if lock was successful, False otherwise.
696 """
697 try:
698 self.run('modify_hosts',
699 host_filter_data={'hostname': host},
700 update_data={'locked': True,
701 'lock_reason': lock_reason})
702 except Exception:
703 return not fail_if_locked
704 return True
705
706
707 def unlock_hosts(self, locked_hosts):
708 """
709 Unlock the hosts.
710
711 Unlocking a host that's already unlocked will do nothing so we don't
712 need any special try-except clause here.
713
714 @param locked_hosts: List of hostnames of hosts to unlock.
715 """
716 self.run('modify_hosts',
717 host_filter_data={'hostname__in': locked_hosts},
718 update_data={'locked': False,
719 'lock_reason': ''})
720
721
mbligh5280e3b2008-12-22 14:39:28 +0000722class TestResults(object):
723 """
724 Container class used to hold the results of the tests for a job
725 """
726 def __init__(self):
727 self.good = []
728 self.fail = []
mbligh451ede12009-02-12 21:54:03 +0000729 self.pending = []
mbligh5280e3b2008-12-22 14:39:28 +0000730
731
732 def add(self, result):
mbligh451ede12009-02-12 21:54:03 +0000733 if result.complete_count > result.pass_count:
734 self.fail.append(result)
735 elif result.incomplete_count > 0:
736 self.pending.append(result)
mbligh5280e3b2008-12-22 14:39:28 +0000737 else:
mbligh451ede12009-02-12 21:54:03 +0000738 self.good.append(result)
mbligh5280e3b2008-12-22 14:39:28 +0000739
740
741class RpcObject(object):
mbligh67647152008-11-19 00:18:14 +0000742 """
743 Generic object used to construct python objects from rpc calls
744 """
745 def __init__(self, afe, hash):
746 self.afe = afe
747 self.hash = hash
748 self.__dict__.update(hash)
749
750
751 def __str__(self):
752 return dump_object(self.__repr__(), self)
753
754
mbligh1354c9d2008-12-22 14:56:13 +0000755class ControlFile(RpcObject):
756 """
757 AFE control file object
758
759 Fields: synch_count, dependencies, control_file, is_server
760 """
761 def __repr__(self):
762 return 'CONTROL FILE: %s' % self.control_file
763
764
mbligh5280e3b2008-12-22 14:39:28 +0000765class Label(RpcObject):
mbligh67647152008-11-19 00:18:14 +0000766 """
767 AFE label object
768
769 Fields:
770 name, invalid, platform, kernel_config, id, only_if_needed
771 """
772 def __repr__(self):
773 return 'LABEL: %s' % self.name
774
775
776 def add_hosts(self, hosts):
Prathmesh Prabhuef7e5b02017-11-08 11:57:47 -0800777 # We must use the label's name instead of the id because label ids are
Derek Beckett7ff6f742020-12-01 11:34:45 -0800778 # not consistent across main-shard.
Prathmesh Prabhuef7e5b02017-11-08 11:57:47 -0800779 return self.afe.run('label_add_hosts', id=self.name, hosts=hosts)
mbligh67647152008-11-19 00:18:14 +0000780
781
782 def remove_hosts(self, hosts):
Prathmesh Prabhuef7e5b02017-11-08 11:57:47 -0800783 # We must use the label's name instead of the id because label ids are
Derek Beckett7ff6f742020-12-01 11:34:45 -0800784 # not consistent across main-shard.
Prathmesh Prabhuef7e5b02017-11-08 11:57:47 -0800785 return self.afe.run('label_remove_hosts', id=self.name, hosts=hosts)
mbligh67647152008-11-19 00:18:14 +0000786
787
mbligh5280e3b2008-12-22 14:39:28 +0000788class Acl(RpcObject):
mbligh67647152008-11-19 00:18:14 +0000789 """
790 AFE acl object
791
792 Fields:
793 users, hosts, description, name, id
794 """
795 def __repr__(self):
796 return 'ACL: %s' % self.name
797
798
799 def add_hosts(self, hosts):
800 self.afe.log('Adding hosts %s to ACL %s' % (hosts, self.name))
801 return self.afe.run('acl_group_add_hosts', self.id, hosts)
802
803
804 def remove_hosts(self, hosts):
805 self.afe.log('Removing hosts %s from ACL %s' % (hosts, self.name))
806 return self.afe.run('acl_group_remove_hosts', self.id, hosts)
807
808
mbligh54459c72009-01-21 19:26:44 +0000809 def add_users(self, users):
810 self.afe.log('Adding users %s to ACL %s' % (users, self.name))
811 return self.afe.run('acl_group_add_users', id=self.name, users=users)
812
813
mbligh5280e3b2008-12-22 14:39:28 +0000814class Job(RpcObject):
mbligh67647152008-11-19 00:18:14 +0000815 """
816 AFE job object
817
818 Fields:
819 name, control_file, control_type, synch_count, reboot_before,
820 run_verify, priority, email_list, created_on, dependencies,
821 timeout, owner, reboot_after, id
822 """
823 def __repr__(self):
824 return 'JOB: %s' % self.id
825
826
mbligh5280e3b2008-12-22 14:39:28 +0000827class JobStatus(RpcObject):
mbligh67647152008-11-19 00:18:14 +0000828 """
829 AFE job_status object
830
831 Fields:
832 status, complete, deleted, meta_host, host, active, execution_subdir, id
833 """
834 def __init__(self, afe, hash):
MK Ryu1b2d7f92015-02-24 17:45:02 -0800835 super(JobStatus, self).__init__(afe, hash)
mbligh5280e3b2008-12-22 14:39:28 +0000836 self.job = Job(afe, self.job)
Dale Curtis8adf7892011-09-08 16:13:36 -0700837 if getattr(self, 'host'):
mbligh99b24f42009-06-08 16:45:55 +0000838 self.host = Host(afe, self.host)
mbligh67647152008-11-19 00:18:14 +0000839
840
841 def __repr__(self):
mbligh451ede12009-02-12 21:54:03 +0000842 if self.host and self.host.hostname:
843 hostname = self.host.hostname
844 else:
845 hostname = 'None'
846 return 'JOB STATUS: %s-%s' % (self.job.id, hostname)
mbligh67647152008-11-19 00:18:14 +0000847
848
MK Ryu1b2d7f92015-02-24 17:45:02 -0800849class SpecialTask(RpcObject):
850 """
851 AFE special task object
852 """
853 def __init__(self, afe, hash):
854 super(SpecialTask, self).__init__(afe, hash)
855 self.host = Host(afe, self.host)
856
857
858 def __repr__(self):
859 return 'SPECIAL TASK: %s' % self.id
860
861
mbligh5280e3b2008-12-22 14:39:28 +0000862class Host(RpcObject):
mbligh67647152008-11-19 00:18:14 +0000863 """
864 AFE host object
865
866 Fields:
867 status, lock_time, locked_by, locked, hostname, invalid,
Allen Lie4c08272017-02-01 16:40:53 -0800868 labels, platform, protection, dirty, id
mbligh67647152008-11-19 00:18:14 +0000869 """
870 def __repr__(self):
871 return 'HOST OBJECT: %s' % self.hostname
872
873
874 def show(self):
875 labels = list(set(self.labels) - set([self.platform]))
Derek Beckett63e1c442020-08-11 14:49:47 -0700876 print('%-6s %-7s %-7s %-16s %s' % (self.hostname, self.status,
mbligh67647152008-11-19 00:18:14 +0000877 self.locked, self.platform,
Derek Beckett63e1c442020-08-11 14:49:47 -0700878 ', '.join(labels)))
mbligh67647152008-11-19 00:18:14 +0000879
880
mbligh54459c72009-01-21 19:26:44 +0000881 def delete(self):
882 return self.afe.run('delete_host', id=self.id)
883
884
mbligh6463c4b2009-01-30 00:33:37 +0000885 def modify(self, **dargs):
886 return self.afe.run('modify_host', id=self.id, **dargs)
887
888
mbligh67647152008-11-19 00:18:14 +0000889 def get_acls(self):
890 return self.afe.get_acls(hosts__hostname=self.hostname)
891
892
893 def add_acl(self, acl_name):
894 self.afe.log('Adding ACL %s to host %s' % (acl_name, self.hostname))
895 return self.afe.run('acl_group_add_hosts', id=acl_name,
896 hosts=[self.hostname])
897
898
899 def remove_acl(self, acl_name):
900 self.afe.log('Removing ACL %s from host %s' % (acl_name, self.hostname))
901 return self.afe.run('acl_group_remove_hosts', id=acl_name,
902 hosts=[self.hostname])
903
904
905 def get_labels(self):
906 return self.afe.get_labels(host__hostname__in=[self.hostname])
907
908
909 def add_labels(self, labels):
910 self.afe.log('Adding labels %s to host %s' % (labels, self.hostname))
911 return self.afe.run('host_add_labels', id=self.id, labels=labels)
912
913
914 def remove_labels(self, labels):
915 self.afe.log('Removing labels %s from host %s' % (labels,self.hostname))
916 return self.afe.run('host_remove_labels', id=self.id, labels=labels)
mbligh5b618382008-12-03 15:24:01 +0000917
918
Allen Lif74957d2017-11-20 17:46:48 -0800919 def is_available(self):
920 """Check whether DUT host is available.
921
922 @return: bool
923 """
924 return not (self.locked
925 or self.status in host_states.UNAVAILABLE_STATES)
926
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