blob: 2e2ecccd172bf779a3d552827cafe444a57f40a8 [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 -080023from chromite.lib import metrics
24
mbligh67647152008-11-19 00:18:14 +000025from autotest_lib.frontend.afe import rpc_client_lib
Dan Shie8e0c052015-09-01 00:27:27 -070026from autotest_lib.client.common_lib import control_data
mbligh37eceaa2008-12-15 22:56:37 +000027from autotest_lib.client.common_lib import global_config
Allen Li352b86a2016-12-14 12:11:27 -080028from autotest_lib.client.common_lib import priorities
mbligh67647152008-11-19 00:18:14 +000029from autotest_lib.client.common_lib import utils
Scott Zawalski63470dd2012-09-05 00:49:43 -040030from autotest_lib.tko import db
31
32
mbligh4e576612008-12-22 14:56:36 +000033try:
34 from autotest_lib.server.site_common import site_utils as server_utils
35except:
36 from autotest_lib.server import utils as server_utils
37form_ntuples_from_machines = server_utils.form_ntuples_from_machines
mbligh67647152008-11-19 00:18:14 +000038
mbligh37eceaa2008-12-15 22:56:37 +000039GLOBAL_CONFIG = global_config.global_config
40DEFAULT_SERVER = 'autotest'
41
Dan Shie8e0c052015-09-01 00:27:27 -070042
mbligh67647152008-11-19 00:18:14 +000043def dump_object(header, obj):
44 """
45 Standard way to print out the frontend objects (eg job, host, acl, label)
46 in a human-readable fashion for debugging
47 """
48 result = header + '\n'
49 for key in obj.hash:
50 if key == 'afe' or key == 'hash':
51 continue
52 result += '%20s: %s\n' % (key, obj.hash[key])
53 return result
54
55
mbligh5280e3b2008-12-22 14:39:28 +000056class RpcClient(object):
mbligh67647152008-11-19 00:18:14 +000057 """
mbligh451ede12009-02-12 21:54:03 +000058 Abstract RPC class for communicating with the autotest frontend
59 Inherited for both TKO and AFE uses.
mbligh67647152008-11-19 00:18:14 +000060
mbligh1ef218d2009-08-03 16:57:56 +000061 All the constructors go in the afe / tko class.
mbligh451ede12009-02-12 21:54:03 +000062 Manipulating methods go in the object classes themselves
mbligh67647152008-11-19 00:18:14 +000063 """
mbligh99b24f42009-06-08 16:45:55 +000064 def __init__(self, path, user, server, print_log, debug, reply_debug):
mbligh67647152008-11-19 00:18:14 +000065 """
mbligh451ede12009-02-12 21:54:03 +000066 Create a cached instance of a connection to the frontend
mbligh67647152008-11-19 00:18:14 +000067
68 user: username to connect as
mbligh451ede12009-02-12 21:54:03 +000069 server: frontend server to connect to
mbligh67647152008-11-19 00:18:14 +000070 print_log: pring a logging message to stdout on every operation
71 debug: print out all RPC traffic
72 """
Dan Shiff78f112015-06-12 13:34:02 -070073 if not user and utils.is_in_container():
74 user = GLOBAL_CONFIG.get_config_value('SSP', 'user', default=None)
mblighc31e4022008-12-11 19:32:30 +000075 if not user:
mblighdb59e3c2009-11-21 01:45:18 +000076 user = getpass.getuser()
mbligh451ede12009-02-12 21:54:03 +000077 if not server:
mbligh475f7762009-01-30 00:34:04 +000078 if 'AUTOTEST_WEB' in os.environ:
mbligh451ede12009-02-12 21:54:03 +000079 server = os.environ['AUTOTEST_WEB']
mbligh475f7762009-01-30 00:34:04 +000080 else:
mbligh451ede12009-02-12 21:54:03 +000081 server = GLOBAL_CONFIG.get_config_value('SERVER', 'hostname',
82 default=DEFAULT_SERVER)
83 self.server = server
mbligh67647152008-11-19 00:18:14 +000084 self.user = user
85 self.print_log = print_log
86 self.debug = debug
mbligh99b24f42009-06-08 16:45:55 +000087 self.reply_debug = reply_debug
Scott Zawalski347aaf42012-04-03 16:33:00 -040088 headers = {'AUTHORIZATION': self.user}
89 rpc_server = 'http://' + server + path
mbligh1354c9d2008-12-22 14:56:13 +000090 if debug:
91 print 'SERVER: %s' % rpc_server
92 print 'HEADERS: %s' % headers
mbligh67647152008-11-19 00:18:14 +000093 self.proxy = rpc_client_lib.get_proxy(rpc_server, headers=headers)
94
95
96 def run(self, call, **dargs):
97 """
98 Make a RPC call to the AFE server
99 """
100 rpc_call = getattr(self.proxy, call)
101 if self.debug:
102 print 'DEBUG: %s %s' % (call, dargs)
mbligh451ede12009-02-12 21:54:03 +0000103 try:
mbligh99b24f42009-06-08 16:45:55 +0000104 result = utils.strip_unicode(rpc_call(**dargs))
105 if self.reply_debug:
106 print result
107 return result
mbligh451ede12009-02-12 21:54:03 +0000108 except Exception:
mbligh451ede12009-02-12 21:54:03 +0000109 raise
mbligh67647152008-11-19 00:18:14 +0000110
111
112 def log(self, message):
113 if self.print_log:
114 print message
115
116
jamesrenc3940222010-02-19 21:57:37 +0000117class Planner(RpcClient):
118 def __init__(self, user=None, server=None, print_log=True, debug=False,
119 reply_debug=False):
120 super(Planner, self).__init__(path='/planner/server/rpc/',
121 user=user,
122 server=server,
123 print_log=print_log,
124 debug=debug,
125 reply_debug=reply_debug)
126
127
mbligh5280e3b2008-12-22 14:39:28 +0000128class TKO(RpcClient):
mbligh99b24f42009-06-08 16:45:55 +0000129 def __init__(self, user=None, server=None, print_log=True, debug=False,
130 reply_debug=False):
Scott Zawalski347aaf42012-04-03 16:33:00 -0400131 super(TKO, self).__init__(path='/new_tko/server/noauth/rpc/',
mbligh99b24f42009-06-08 16:45:55 +0000132 user=user,
133 server=server,
134 print_log=print_log,
135 debug=debug,
136 reply_debug=reply_debug)
Scott Zawalski63470dd2012-09-05 00:49:43 -0400137 self._db = None
138
139
Allen Li327e6fd2016-11-22 13:45:41 -0800140 @metrics.SecondsTimerDecorator(
Prathmesh Prabhua7556a92017-02-01 14:18:41 -0800141 'chromeos/autotest/tko/get_job_status_duration')
Scott Zawalski63470dd2012-09-05 00:49:43 -0400142 def get_job_test_statuses_from_db(self, job_id):
143 """Get job test statuses from the database.
144
145 Retrieve a set of fields from a job that reflect the status of each test
146 run within a job.
147 fields retrieved: status, test_name, reason, test_started_time,
148 test_finished_time, afe_job_id, job_owner, hostname.
149
150 @param job_id: The afe job id to look up.
151 @returns a TestStatus object of the resulting information.
152 """
153 if self._db is None:
Dan Shie8e0c052015-09-01 00:27:27 -0700154 self._db = db.db()
Fang Deng5c508332014-03-19 10:26:00 -0700155 fields = ['status', 'test_name', 'subdir', 'reason',
156 'test_started_time', 'test_finished_time', 'afe_job_id',
157 'job_owner', 'hostname', 'job_tag']
Scott Zawalski63470dd2012-09-05 00:49:43 -0400158 table = 'tko_test_view_2'
159 where = 'job_tag like "%s-%%"' % job_id
160 test_status = []
161 # Run commit before we query to ensure that we are pulling the latest
162 # results.
163 self._db.commit()
164 for entry in self._db.select(','.join(fields), table, (where, None)):
165 status_dict = {}
166 for key,value in zip(fields, entry):
167 # All callers expect values to be a str object.
168 status_dict[key] = str(value)
169 # id is used by TestStatus to uniquely identify each Test Status
170 # obj.
171 status_dict['id'] = [status_dict['reason'], status_dict['hostname'],
172 status_dict['test_name']]
173 test_status.append(status_dict)
174
175 return [TestStatus(self, e) for e in test_status]
mblighc31e4022008-12-11 19:32:30 +0000176
177
178 def get_status_counts(self, job, **data):
179 entries = self.run('get_status_counts',
mbligh1ef218d2009-08-03 16:57:56 +0000180 group_by=['hostname', 'test_name', 'reason'],
mblighc31e4022008-12-11 19:32:30 +0000181 job_tag__startswith='%s-' % job, **data)
mbligh5280e3b2008-12-22 14:39:28 +0000182 return [TestStatus(self, e) for e in entries['groups']]
mblighc31e4022008-12-11 19:32:30 +0000183
184
Richard Barnette260cbd02016-10-06 12:23:28 -0700185class _StableVersionMap(object):
186 """
187 A mapping from board names to strings naming software versions.
188
189 The mapping is meant to allow finding a nominally "stable" version
190 of software associated with a given board. The mapping identifies
191 specific versions of software that should be installed during
192 operations such as repair.
193
194 Conceptually, there are multiple version maps, each handling
195 different types of image. For instance, a single board may have
196 both a stable OS image (e.g. for CrOS), and a separate stable
197 firmware image.
198
199 Each different type of image requires a certain amount of special
200 handling, implemented by a subclass of `StableVersionMap`. The
201 subclasses take care of pre-processing of arguments, delegating
202 actual RPC calls to this superclass.
203
204 @property _afe AFE object through which to make the actual RPC
205 calls.
206 @property _android Value of the `android` parameter to be passed
207 when calling the `get_stable_version` RPC.
208 """
209
210 # DEFAULT_BOARD - The stable_version RPC API recognizes this special
211 # name as a mapping to use when no specific mapping for a board is
212 # present. This default mapping is only allowed for CrOS image
213 # types; other image type subclasses exclude it.
214 #
215 # TODO(jrbarnette): This value is copied from
216 # site_utils.stable_version_utils, because if we import that
217 # module here, it breaks unit tests. Something about the Django
218 # setup...
219 DEFAULT_BOARD = 'DEFAULT'
220
221
222 def __init__(self, afe, android):
223 self._afe = afe
224 self._android = android
225
226
227 def get_all_versions(self):
228 """
229 Get all mappings in the stable versions table.
230
231 Extracts the full content of the `stable_version` table
232 in the AFE database, and returns it as a dictionary
233 mapping board names to version strings.
234
235 @return A dictionary mapping board names to version strings.
236 """
237 return self._afe.run('get_all_stable_versions')
238
239
240 def get_version(self, board):
241 """
242 Get the mapping of one board in the stable versions table.
243
244 Look up and return the version mapped to the given board in the
245 `stable_versions` table in the AFE database.
246
247 @param board The board to be looked up.
248
249 @return The version mapped for the given board.
250 """
251 return self._afe.run('get_stable_version',
252 board=board, android=self._android)
253
254
255 def set_version(self, board, version):
256 """
257 Change the mapping of one board in the stable versions table.
258
259 Set the mapping in the `stable_versions` table in the AFE
260 database for the given board to the given version.
261
262 @param board The board to be updated.
263 @param version The new version to be assigned to the board.
264 """
265 self._afe.run('set_stable_version',
266 version=version, board=board)
267
268
269 def delete_version(self, board):
270 """
271 Remove the mapping of one board in the stable versions table.
272
273 Remove the mapping in the `stable_versions` table in the AFE
274 database for the given board.
275
276 @param board The board to be updated.
277 """
278 self._afe.run('delete_stable_version', board=board)
279
280
281class _OSVersionMap(_StableVersionMap):
282 """
283 Abstract stable version mapping for full OS images of various types.
284 """
285
286 def get_all_versions(self):
Richard Barnettee50453e2016-10-10 16:43:44 -0700287 # TODO(jrbarnette): We exclude non-OS (i.e. firmware) version
288 # mappings, but the returned dict doesn't distinguish CrOS
289 # boards from Android boards; both will be present, and the
290 # subclass can't distinguish them.
Richard Barnette260cbd02016-10-06 12:23:28 -0700291 #
292 # Ultimately, the right fix is to move knowledge of image type
293 # over to the RPC server side.
294 #
295 versions = super(_OSVersionMap, self).get_all_versions()
296 for board in versions.keys():
297 if '/' in board:
298 del versions[board]
299 return versions
300
301
302class _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
312 def __init__(self, afe):
313 super(_CrosVersionMap, self).__init__(afe, False)
314
Richard Barnette728e36f2016-11-03 16:04:29 -0700315 @staticmethod
316 def format_image_name(board, version):
317 """
318 Return an image name for a given `board` and `version`.
319
320 This formats `board` and `version` into a string identifying an
321 image file. The string represents part of a URL for access to
322 the image.
323
324 The returned image name is typically of a form like
325 "falco-release/R55-8872.44.0".
326 """
327 build_pattern = GLOBAL_CONFIG.get_config_value(
328 'CROS', 'stable_build_pattern')
329 return build_pattern % (board, version)
Richard Barnette260cbd02016-10-06 12:23:28 -0700330
Richard Barnette383ef9c2016-12-13 11:56:49 -0800331 def get_image_name(self, board):
332 """
333 Return the full image name of the stable version for `board`.
334
335 This finds the stable version for `board`, and returns a string
Richard Barnette728e36f2016-11-03 16:04:29 -0700336 identifying the associated image as for `format_image_name()`,
337 above.
Richard Barnette383ef9c2016-12-13 11:56:49 -0800338
339 @return A string identifying the image file for the stable
340 image for `board`.
341 """
Richard Barnette728e36f2016-11-03 16:04:29 -0700342 return self.format_image_name(board, self.get_version(board))
Richard Barnette260cbd02016-10-06 12:23:28 -0700343
344
345class _AndroidVersionMap(_OSVersionMap):
346 """
347 Stable version mapping for Android release images.
348
349 This class manages a mapping of Android/Brillo board names to
350 known-good images.
351 """
352
353 def __init__(self, afe):
354 super(_AndroidVersionMap, self).__init__(afe, True)
355
356
357 def get_all_versions(self):
358 versions = super(_AndroidVersionMap, self).get_all_versions()
359 del versions[self.DEFAULT_BOARD]
360 return versions
361
362
Richard Barnettee50453e2016-10-10 16:43:44 -0700363class _SuffixHackVersionMap(_StableVersionMap):
Richard Barnette260cbd02016-10-06 12:23:28 -0700364 """
Richard Barnettee50453e2016-10-10 16:43:44 -0700365 Abstract super class for mappings using a pseudo-board name.
Richard Barnette260cbd02016-10-06 12:23:28 -0700366
Richard Barnettee50453e2016-10-10 16:43:44 -0700367 For non-OS image type mappings, we look them up in the
368 `stable_versions` table by constructing a "pseudo-board" from the
369 real board name plus a suffix string that identifies the image type.
370 So, for instance the name "lulu/firmware" is used to look up the
371 FAFT firmware version for lulu boards.
Richard Barnette260cbd02016-10-06 12:23:28 -0700372 """
373
Richard Barnettee50453e2016-10-10 16:43:44 -0700374 # _SUFFIX - The suffix used in constructing the "pseudo-board"
375 # lookup key. Each subclass must define this value for itself.
Richard Barnette260cbd02016-10-06 12:23:28 -0700376 #
Richard Barnettee50453e2016-10-10 16:43:44 -0700377 _SUFFIX = None
Richard Barnette260cbd02016-10-06 12:23:28 -0700378
379 def __init__(self, afe):
Richard Barnettee50453e2016-10-10 16:43:44 -0700380 super(_SuffixHackVersionMap, self).__init__(afe, False)
Richard Barnette260cbd02016-10-06 12:23:28 -0700381
382
383 def get_all_versions(self):
Richard Barnettee50453e2016-10-10 16:43:44 -0700384 # Get all the mappings from the AFE, extract just the mappings
385 # with our suffix, and replace the pseudo-board name keys with
Richard Barnette260cbd02016-10-06 12:23:28 -0700386 # the real board names.
387 #
388 all_versions = super(
Richard Barnettee50453e2016-10-10 16:43:44 -0700389 _SuffixHackVersionMap, self).get_all_versions()
Richard Barnette260cbd02016-10-06 12:23:28 -0700390 return {
391 board[0 : -len(self._SUFFIX)]: all_versions[board]
392 for board in all_versions.keys()
393 if board.endswith(self._SUFFIX)
394 }
395
396
397 def get_version(self, board):
Richard Barnettee50453e2016-10-10 16:43:44 -0700398 board += self._SUFFIX
399 return super(_SuffixHackVersionMap, self).get_version(board)
400
401
402 def set_version(self, board, version):
403 board += self._SUFFIX
404 super(_SuffixHackVersionMap, self).set_version(board, version)
405
406
407 def delete_version(self, board):
408 board += self._SUFFIX
409 super(_SuffixHackVersionMap, self).delete_version(board)
410
411
412class _FAFTVersionMap(_SuffixHackVersionMap):
413 """
414 Stable version mapping for firmware versions used in FAFT repair.
415
416 When DUTs used for FAFT fail repair, stable firmware may need to be
417 flashed directly from original tarballs. The FAFT firmware version
418 mapping finds the appropriate tarball for a given board.
419 """
420
421 _SUFFIX = '/firmware'
422
423 def get_version(self, board):
424 # If there's no mapping for `board`, the lookup will return the
425 # default CrOS version mapping. To eliminate that case, we
426 # require a '/' character in the version, since CrOS versions
427 # won't match that.
Richard Barnette260cbd02016-10-06 12:23:28 -0700428 #
429 # TODO(jrbarnette): This is, of course, a hack. Ultimately,
430 # the right fix is to move handling to the RPC server side.
431 #
Richard Barnette260cbd02016-10-06 12:23:28 -0700432 version = super(_FAFTVersionMap, self).get_version(board)
433 return version if '/' in version else None
434
435
Richard Barnettee50453e2016-10-10 16:43:44 -0700436class _FirmwareVersionMap(_SuffixHackVersionMap):
437 """
438 Stable version mapping for firmware supplied in Chrome OS images.
Richard Barnette260cbd02016-10-06 12:23:28 -0700439
Richard Barnettee50453e2016-10-10 16:43:44 -0700440 A Chrome OS image bundles a version of the firmware that the
441 device should update to when the OS version is installed during
442 AU.
Richard Barnette260cbd02016-10-06 12:23:28 -0700443
Richard Barnettee50453e2016-10-10 16:43:44 -0700444 Test images suppress the firmware update during AU. Instead, during
445 repair and verify we check installed firmware on a DUT, compare it
446 against the stable version mapping for the board, and update when
447 the DUT is out-of-date.
448 """
449
450 _SUFFIX = '/rwfw'
451
452 def get_version(self, board):
453 # If there's no mapping for `board`, the lookup will return the
454 # default CrOS version mapping. To eliminate that case, we
455 # require the version start with "Google_", since CrOS versions
456 # won't match that.
457 #
458 # TODO(jrbarnette): This is, of course, a hack. Ultimately,
459 # the right fix is to move handling to the RPC server side.
460 #
461 version = super(_FirmwareVersionMap, self).get_version(board)
462 return version if version.startswith('Google_') else None
Richard Barnette260cbd02016-10-06 12:23:28 -0700463
464
mbligh5280e3b2008-12-22 14:39:28 +0000465class AFE(RpcClient):
mbligh1ef218d2009-08-03 16:57:56 +0000466
Richard Barnette260cbd02016-10-06 12:23:28 -0700467 # Known image types for stable version mapping objects.
468 # CROS_IMAGE_TYPE - Mappings for Chrome OS images.
469 # FAFT_IMAGE_TYPE - Mappings for Firmware images for FAFT repair.
Richard Barnettee50453e2016-10-10 16:43:44 -0700470 # FIRMWARE_IMAGE_TYPE - Mappings for released RW Firmware images.
Richard Barnette260cbd02016-10-06 12:23:28 -0700471 # ANDROID_IMAGE_TYPE - Mappings for Android images.
472 #
473 CROS_IMAGE_TYPE = 'cros'
474 FAFT_IMAGE_TYPE = 'faft'
Richard Barnettee50453e2016-10-10 16:43:44 -0700475 FIRMWARE_IMAGE_TYPE = 'firmware'
Richard Barnette260cbd02016-10-06 12:23:28 -0700476 ANDROID_IMAGE_TYPE = 'android'
477
478 _IMAGE_MAPPING_CLASSES = {
479 CROS_IMAGE_TYPE: _CrosVersionMap,
480 FAFT_IMAGE_TYPE: _FAFTVersionMap,
Richard Barnettee50453e2016-10-10 16:43:44 -0700481 FIRMWARE_IMAGE_TYPE: _FirmwareVersionMap,
Richard Barnette260cbd02016-10-06 12:23:28 -0700482 ANDROID_IMAGE_TYPE: _AndroidVersionMap
483 }
484
485
Prathmesh Prabhu6d5ba592017-01-05 13:56:04 -0800486 def __init__(self, user=None, server=None, print_log=True, debug=False,
487 reply_debug=False, job=None):
488 self.job = job
489 super(AFE, self).__init__(path='/afe/server/noauth/rpc/',
490 user=user,
491 server=server,
492 print_log=print_log,
493 debug=debug,
494 reply_debug=reply_debug)
495
496
Richard Barnette260cbd02016-10-06 12:23:28 -0700497 def get_stable_version_map(self, image_type):
498 """
499 Return a stable version mapping for the given image type.
500
501 @return An object mapping board names to version strings for
502 software of the given image type.
503 """
504 return self._IMAGE_MAPPING_CLASSES[image_type](self)
505
506
mbligh67647152008-11-19 00:18:14 +0000507 def host_statuses(self, live=None):
jamesren121eee62010-04-13 19:10:12 +0000508 dead_statuses = ['Repair Failed', 'Repairing']
mbligh67647152008-11-19 00:18:14 +0000509 statuses = self.run('get_static_data')['host_statuses']
510 if live == True:
mblighc2847b72009-03-25 19:32:20 +0000511 return list(set(statuses) - set(dead_statuses))
mbligh67647152008-11-19 00:18:14 +0000512 if live == False:
513 return dead_statuses
514 else:
515 return statuses
516
517
mbligh71094012009-12-19 05:35:21 +0000518 @staticmethod
519 def _dict_for_host_query(hostnames=(), status=None, label=None):
520 query_args = {}
mbligh4e545a52009-12-19 05:30:39 +0000521 if hostnames:
522 query_args['hostname__in'] = hostnames
523 if status:
524 query_args['status'] = status
525 if label:
526 query_args['labels__name'] = label
mbligh71094012009-12-19 05:35:21 +0000527 return query_args
528
529
530 def get_hosts(self, hostnames=(), status=None, label=None, **dargs):
531 query_args = dict(dargs)
532 query_args.update(self._dict_for_host_query(hostnames=hostnames,
533 status=status,
534 label=label))
535 hosts = self.run('get_hosts', **query_args)
536 return [Host(self, h) for h in hosts]
537
538
539 def get_hostnames(self, status=None, label=None, **dargs):
540 """Like get_hosts() but returns hostnames instead of Host objects."""
541 # This implementation can be replaced with a more efficient one
542 # that does not query for entire host objects in the future.
543 return [host_obj.hostname for host_obj in
544 self.get_hosts(status=status, label=label, **dargs)]
545
546
547 def reverify_hosts(self, hostnames=(), status=None, label=None):
548 query_args = dict(locked=False,
549 aclgroup__users__login=self.user)
550 query_args.update(self._dict_for_host_query(hostnames=hostnames,
551 status=status,
552 label=label))
mbligh4e545a52009-12-19 05:30:39 +0000553 return self.run('reverify_hosts', **query_args)
554
555
mbligh67647152008-11-19 00:18:14 +0000556 def create_host(self, hostname, **dargs):
mbligh54459c72009-01-21 19:26:44 +0000557 id = self.run('add_host', hostname=hostname, **dargs)
mbligh67647152008-11-19 00:18:14 +0000558 return self.get_hosts(id=id)[0]
559
560
MK Ryuacf35922014-10-03 14:56:49 -0700561 def get_host_attribute(self, attr, **dargs):
562 host_attrs = self.run('get_host_attribute', attribute=attr, **dargs)
563 return [HostAttribute(self, a) for a in host_attrs]
564
565
Chris Masone8abb6fc2012-01-31 09:27:36 -0800566 def set_host_attribute(self, attr, val, **dargs):
567 self.run('set_host_attribute', attribute=attr, value=val, **dargs)
568
569
mbligh67647152008-11-19 00:18:14 +0000570 def get_labels(self, **dargs):
571 labels = self.run('get_labels', **dargs)
mbligh5280e3b2008-12-22 14:39:28 +0000572 return [Label(self, l) for l in labels]
mbligh67647152008-11-19 00:18:14 +0000573
574
575 def create_label(self, name, **dargs):
mbligh54459c72009-01-21 19:26:44 +0000576 id = self.run('add_label', name=name, **dargs)
mbligh67647152008-11-19 00:18:14 +0000577 return self.get_labels(id=id)[0]
578
579
580 def get_acls(self, **dargs):
581 acls = self.run('get_acl_groups', **dargs)
mbligh5280e3b2008-12-22 14:39:28 +0000582 return [Acl(self, a) for a in acls]
mbligh67647152008-11-19 00:18:14 +0000583
584
585 def create_acl(self, name, **dargs):
mbligh54459c72009-01-21 19:26:44 +0000586 id = self.run('add_acl_group', name=name, **dargs)
mbligh67647152008-11-19 00:18:14 +0000587 return self.get_acls(id=id)[0]
588
589
mbligh54459c72009-01-21 19:26:44 +0000590 def get_users(self, **dargs):
591 users = self.run('get_users', **dargs)
592 return [User(self, u) for u in users]
593
594
mbligh1354c9d2008-12-22 14:56:13 +0000595 def generate_control_file(self, tests, **dargs):
596 ret = self.run('generate_control_file', tests=tests, **dargs)
597 return ControlFile(self, ret)
598
599
mbligh67647152008-11-19 00:18:14 +0000600 def get_jobs(self, summary=False, **dargs):
601 if summary:
602 jobs_data = self.run('get_jobs_summary', **dargs)
603 else:
604 jobs_data = self.run('get_jobs', **dargs)
mblighafbba0c2009-06-08 16:44:45 +0000605 jobs = []
606 for j in jobs_data:
607 job = Job(self, j)
608 # Set up some extra information defaults
609 job.testname = re.sub('\s.*', '', job.name) # arbitrary default
610 job.platform_results = {}
611 job.platform_reasons = {}
612 jobs.append(job)
613 return jobs
mbligh67647152008-11-19 00:18:14 +0000614
615
616 def get_host_queue_entries(self, **data):
617 entries = self.run('get_host_queue_entries', **data)
mblighf9e35862009-02-26 01:03:11 +0000618 job_statuses = [JobStatus(self, e) for e in entries]
mbligh99b24f42009-06-08 16:45:55 +0000619
620 # Sadly, get_host_queue_entries doesn't return platforms, we have
621 # to get those back from an explicit get_hosts queury, then patch
622 # the new host objects back into the host list.
623 hostnames = [s.host.hostname for s in job_statuses if s.host]
624 host_hash = {}
625 for host in self.get_hosts(hostname__in=hostnames):
626 host_hash[host.hostname] = host
627 for status in job_statuses:
628 if status.host:
Fang Deng97dafbc2015-04-23 23:06:18 -0700629 status.host = host_hash.get(status.host.hostname)
mblighf9e35862009-02-26 01:03:11 +0000630 # filter job statuses that have either host or meta_host
631 return [status for status in job_statuses if (status.host or
632 status.meta_host)]
mbligh67647152008-11-19 00:18:14 +0000633
634
MK Ryu1b2d7f92015-02-24 17:45:02 -0800635 def get_special_tasks(self, **data):
636 tasks = self.run('get_special_tasks', **data)
637 return [SpecialTask(self, t) for t in tasks]
638
639
J. Richard Barnette9f10c9f2015-04-13 16:44:50 -0700640 def get_host_special_tasks(self, host_id, **data):
641 tasks = self.run('get_host_special_tasks',
642 host_id=host_id, **data)
643 return [SpecialTask(self, t) for t in tasks]
644
645
J. Richard Barnette8dbd6d32015-05-01 11:01:12 -0700646 def get_host_status_task(self, host_id, end_time):
647 task = self.run('get_host_status_task',
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700648 host_id=host_id, end_time=end_time)
J. Richard Barnettebc9a7952015-04-16 17:43:27 -0700649 return SpecialTask(self, task) if task else None
650
651
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700652 def get_host_diagnosis_interval(self, host_id, end_time, success):
653 return self.run('get_host_diagnosis_interval',
654 host_id=host_id, end_time=end_time,
655 success=success)
656
657
Allen Li352b86a2016-12-14 12:11:27 -0800658 def create_job(self, control_file, name=' ',
659 priority=priorities.Priority.DEFAULT,
660 control_type=control_data.CONTROL_TYPE_NAMES.CLIENT,
661 **dargs):
mbligh67647152008-11-19 00:18:14 +0000662 id = self.run('create_job', name=name, priority=priority,
663 control_file=control_file, control_type=control_type, **dargs)
664 return self.get_jobs(id=id)[0]
665
666
Simran Basi01984f52015-10-12 15:36:45 -0700667 def abort_jobs(self, jobs):
668 """Abort a list of jobs.
669
670 Already completed jobs will not be affected.
671
672 @param jobs: List of job ids to abort.
673 """
674 for job in jobs:
675 self.run('abort_host_queue_entries', job_id=job)
676
677
Kevin Cheng19521982016-09-22 12:27:23 -0700678 def get_hosts_by_attribute(self, attribute, value):
679 """
680 Get the list of hosts that share the same host attribute value.
681
682 @param attribute: String of the host attribute to check.
683 @param value: String of the value that is shared between hosts.
684
685 @returns List of hostnames that all have the same host attribute and
686 value.
687 """
688 return self.run('get_hosts_by_attribute',
689 attribute=attribute, value=value)
690
691
692 def lock_host(self, host, lock_reason, fail_if_locked=False):
693 """
694 Lock the given host with the given lock reason.
695
696 Locking a host that's already locked using the 'modify_hosts' rpc
697 will raise an exception. That's why fail_if_locked exists so the
698 caller can determine if the lock succeeded or failed. This will
699 save every caller from wrapping lock_host in a try-except.
700
701 @param host: hostname of host to lock.
702 @param lock_reason: Reason for locking host.
703 @param fail_if_locked: Return False if host is already locked.
704
705 @returns Boolean, True if lock was successful, False otherwise.
706 """
707 try:
708 self.run('modify_hosts',
709 host_filter_data={'hostname': host},
710 update_data={'locked': True,
711 'lock_reason': lock_reason})
712 except Exception:
713 return not fail_if_locked
714 return True
715
716
717 def unlock_hosts(self, locked_hosts):
718 """
719 Unlock the hosts.
720
721 Unlocking a host that's already unlocked will do nothing so we don't
722 need any special try-except clause here.
723
724 @param locked_hosts: List of hostnames of hosts to unlock.
725 """
726 self.run('modify_hosts',
727 host_filter_data={'hostname__in': locked_hosts},
728 update_data={'locked': False,
729 'lock_reason': ''})
730
731
mbligh5280e3b2008-12-22 14:39:28 +0000732class TestResults(object):
733 """
734 Container class used to hold the results of the tests for a job
735 """
736 def __init__(self):
737 self.good = []
738 self.fail = []
mbligh451ede12009-02-12 21:54:03 +0000739 self.pending = []
mbligh5280e3b2008-12-22 14:39:28 +0000740
741
742 def add(self, result):
mbligh451ede12009-02-12 21:54:03 +0000743 if result.complete_count > result.pass_count:
744 self.fail.append(result)
745 elif result.incomplete_count > 0:
746 self.pending.append(result)
mbligh5280e3b2008-12-22 14:39:28 +0000747 else:
mbligh451ede12009-02-12 21:54:03 +0000748 self.good.append(result)
mbligh5280e3b2008-12-22 14:39:28 +0000749
750
751class RpcObject(object):
mbligh67647152008-11-19 00:18:14 +0000752 """
753 Generic object used to construct python objects from rpc calls
754 """
755 def __init__(self, afe, hash):
756 self.afe = afe
757 self.hash = hash
758 self.__dict__.update(hash)
759
760
761 def __str__(self):
762 return dump_object(self.__repr__(), self)
763
764
mbligh1354c9d2008-12-22 14:56:13 +0000765class ControlFile(RpcObject):
766 """
767 AFE control file object
768
769 Fields: synch_count, dependencies, control_file, is_server
770 """
771 def __repr__(self):
772 return 'CONTROL FILE: %s' % self.control_file
773
774
mbligh5280e3b2008-12-22 14:39:28 +0000775class Label(RpcObject):
mbligh67647152008-11-19 00:18:14 +0000776 """
777 AFE label object
778
779 Fields:
780 name, invalid, platform, kernel_config, id, only_if_needed
781 """
782 def __repr__(self):
783 return 'LABEL: %s' % self.name
784
785
786 def add_hosts(self, hosts):
Chris Masone3a560bd2011-11-14 16:53:56 -0800787 return self.afe.run('label_add_hosts', id=self.id, hosts=hosts)
mbligh67647152008-11-19 00:18:14 +0000788
789
790 def remove_hosts(self, hosts):
Chris Masone3a560bd2011-11-14 16:53:56 -0800791 return self.afe.run('label_remove_hosts', id=self.id, hosts=hosts)
mbligh67647152008-11-19 00:18:14 +0000792
793
mbligh5280e3b2008-12-22 14:39:28 +0000794class Acl(RpcObject):
mbligh67647152008-11-19 00:18:14 +0000795 """
796 AFE acl object
797
798 Fields:
799 users, hosts, description, name, id
800 """
801 def __repr__(self):
802 return 'ACL: %s' % self.name
803
804
805 def add_hosts(self, hosts):
806 self.afe.log('Adding hosts %s to ACL %s' % (hosts, self.name))
807 return self.afe.run('acl_group_add_hosts', self.id, hosts)
808
809
810 def remove_hosts(self, hosts):
811 self.afe.log('Removing hosts %s from ACL %s' % (hosts, self.name))
812 return self.afe.run('acl_group_remove_hosts', self.id, hosts)
813
814
mbligh54459c72009-01-21 19:26:44 +0000815 def add_users(self, users):
816 self.afe.log('Adding users %s to ACL %s' % (users, self.name))
817 return self.afe.run('acl_group_add_users', id=self.name, users=users)
818
819
mbligh5280e3b2008-12-22 14:39:28 +0000820class Job(RpcObject):
mbligh67647152008-11-19 00:18:14 +0000821 """
822 AFE job object
823
824 Fields:
825 name, control_file, control_type, synch_count, reboot_before,
826 run_verify, priority, email_list, created_on, dependencies,
827 timeout, owner, reboot_after, id
828 """
829 def __repr__(self):
830 return 'JOB: %s' % self.id
831
832
mbligh5280e3b2008-12-22 14:39:28 +0000833class JobStatus(RpcObject):
mbligh67647152008-11-19 00:18:14 +0000834 """
835 AFE job_status object
836
837 Fields:
838 status, complete, deleted, meta_host, host, active, execution_subdir, id
839 """
840 def __init__(self, afe, hash):
MK Ryu1b2d7f92015-02-24 17:45:02 -0800841 super(JobStatus, self).__init__(afe, hash)
mbligh5280e3b2008-12-22 14:39:28 +0000842 self.job = Job(afe, self.job)
Dale Curtis8adf7892011-09-08 16:13:36 -0700843 if getattr(self, 'host'):
mbligh99b24f42009-06-08 16:45:55 +0000844 self.host = Host(afe, self.host)
mbligh67647152008-11-19 00:18:14 +0000845
846
847 def __repr__(self):
mbligh451ede12009-02-12 21:54:03 +0000848 if self.host and self.host.hostname:
849 hostname = self.host.hostname
850 else:
851 hostname = 'None'
852 return 'JOB STATUS: %s-%s' % (self.job.id, hostname)
mbligh67647152008-11-19 00:18:14 +0000853
854
MK Ryu1b2d7f92015-02-24 17:45:02 -0800855class SpecialTask(RpcObject):
856 """
857 AFE special task object
858 """
859 def __init__(self, afe, hash):
860 super(SpecialTask, self).__init__(afe, hash)
861 self.host = Host(afe, self.host)
862
863
864 def __repr__(self):
865 return 'SPECIAL TASK: %s' % self.id
866
867
mbligh5280e3b2008-12-22 14:39:28 +0000868class Host(RpcObject):
mbligh67647152008-11-19 00:18:14 +0000869 """
870 AFE host object
871
872 Fields:
873 status, lock_time, locked_by, locked, hostname, invalid,
Allen Lie4c08272017-02-01 16:40:53 -0800874 labels, platform, protection, dirty, id
mbligh67647152008-11-19 00:18:14 +0000875 """
876 def __repr__(self):
877 return 'HOST OBJECT: %s' % self.hostname
878
879
880 def show(self):
881 labels = list(set(self.labels) - set([self.platform]))
882 print '%-6s %-7s %-7s %-16s %s' % (self.hostname, self.status,
883 self.locked, self.platform,
884 ', '.join(labels))
885
886
mbligh54459c72009-01-21 19:26:44 +0000887 def delete(self):
888 return self.afe.run('delete_host', id=self.id)
889
890
mbligh6463c4b2009-01-30 00:33:37 +0000891 def modify(self, **dargs):
892 return self.afe.run('modify_host', id=self.id, **dargs)
893
894
mbligh67647152008-11-19 00:18:14 +0000895 def get_acls(self):
896 return self.afe.get_acls(hosts__hostname=self.hostname)
897
898
899 def add_acl(self, acl_name):
900 self.afe.log('Adding ACL %s to host %s' % (acl_name, self.hostname))
901 return self.afe.run('acl_group_add_hosts', id=acl_name,
902 hosts=[self.hostname])
903
904
905 def remove_acl(self, acl_name):
906 self.afe.log('Removing ACL %s from host %s' % (acl_name, self.hostname))
907 return self.afe.run('acl_group_remove_hosts', id=acl_name,
908 hosts=[self.hostname])
909
910
911 def get_labels(self):
912 return self.afe.get_labels(host__hostname__in=[self.hostname])
913
914
915 def add_labels(self, labels):
916 self.afe.log('Adding labels %s to host %s' % (labels, self.hostname))
917 return self.afe.run('host_add_labels', id=self.id, labels=labels)
918
919
920 def remove_labels(self, labels):
921 self.afe.log('Removing labels %s from host %s' % (labels,self.hostname))
922 return self.afe.run('host_remove_labels', id=self.id, labels=labels)
mbligh5b618382008-12-03 15:24:01 +0000923
924
mbligh54459c72009-01-21 19:26:44 +0000925class User(RpcObject):
926 def __repr__(self):
927 return 'USER: %s' % self.login
928
929
mbligh5280e3b2008-12-22 14:39:28 +0000930class TestStatus(RpcObject):
mblighc31e4022008-12-11 19:32:30 +0000931 """
932 TKO test status object
933
934 Fields:
935 test_idx, hostname, testname, id
936 complete_count, incomplete_count, group_count, pass_count
937 """
938 def __repr__(self):
939 return 'TEST STATUS: %s' % self.id
940
941
MK Ryuacf35922014-10-03 14:56:49 -0700942class HostAttribute(RpcObject):
943 """
944 AFE host attribute object
945
946 Fields:
947 id, host, attribute, value
948 """
949 def __repr__(self):
950 return 'HOST ATTRIBUTE %d' % self.id