blob: f5d9899446ea1a42c1ad4f4640222faded0947c6 [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(
141 '/chrome/infra/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):
mbligh17c75e62009-06-08 16:18:21 +0000466 def __init__(self, user=None, server=None, print_log=True, debug=False,
mbligh99b24f42009-06-08 16:45:55 +0000467 reply_debug=False, job=None):
mbligh17c75e62009-06-08 16:18:21 +0000468 self.job = job
Scott Zawalski347aaf42012-04-03 16:33:00 -0400469 super(AFE, self).__init__(path='/afe/server/noauth/rpc/',
mbligh99b24f42009-06-08 16:45:55 +0000470 user=user,
471 server=server,
472 print_log=print_log,
473 debug=debug,
474 reply_debug=reply_debug)
mblighc31e4022008-12-11 19:32:30 +0000475
mbligh1ef218d2009-08-03 16:57:56 +0000476
Richard Barnette260cbd02016-10-06 12:23:28 -0700477 # Known image types for stable version mapping objects.
478 # CROS_IMAGE_TYPE - Mappings for Chrome OS images.
479 # FAFT_IMAGE_TYPE - Mappings for Firmware images for FAFT repair.
Richard Barnettee50453e2016-10-10 16:43:44 -0700480 # FIRMWARE_IMAGE_TYPE - Mappings for released RW Firmware images.
Richard Barnette260cbd02016-10-06 12:23:28 -0700481 # ANDROID_IMAGE_TYPE - Mappings for Android images.
482 #
483 CROS_IMAGE_TYPE = 'cros'
484 FAFT_IMAGE_TYPE = 'faft'
Richard Barnettee50453e2016-10-10 16:43:44 -0700485 FIRMWARE_IMAGE_TYPE = 'firmware'
Richard Barnette260cbd02016-10-06 12:23:28 -0700486 ANDROID_IMAGE_TYPE = 'android'
487
488 _IMAGE_MAPPING_CLASSES = {
489 CROS_IMAGE_TYPE: _CrosVersionMap,
490 FAFT_IMAGE_TYPE: _FAFTVersionMap,
Richard Barnettee50453e2016-10-10 16:43:44 -0700491 FIRMWARE_IMAGE_TYPE: _FirmwareVersionMap,
Richard Barnette260cbd02016-10-06 12:23:28 -0700492 ANDROID_IMAGE_TYPE: _AndroidVersionMap
493 }
494
495
496 def get_stable_version_map(self, image_type):
497 """
498 Return a stable version mapping for the given image type.
499
500 @return An object mapping board names to version strings for
501 software of the given image type.
502 """
503 return self._IMAGE_MAPPING_CLASSES[image_type](self)
504
505
mbligh67647152008-11-19 00:18:14 +0000506 def host_statuses(self, live=None):
jamesren121eee62010-04-13 19:10:12 +0000507 dead_statuses = ['Repair Failed', 'Repairing']
mbligh67647152008-11-19 00:18:14 +0000508 statuses = self.run('get_static_data')['host_statuses']
509 if live == True:
mblighc2847b72009-03-25 19:32:20 +0000510 return list(set(statuses) - set(dead_statuses))
mbligh67647152008-11-19 00:18:14 +0000511 if live == False:
512 return dead_statuses
513 else:
514 return statuses
515
516
mbligh71094012009-12-19 05:35:21 +0000517 @staticmethod
518 def _dict_for_host_query(hostnames=(), status=None, label=None):
519 query_args = {}
mbligh4e545a52009-12-19 05:30:39 +0000520 if hostnames:
521 query_args['hostname__in'] = hostnames
522 if status:
523 query_args['status'] = status
524 if label:
525 query_args['labels__name'] = label
mbligh71094012009-12-19 05:35:21 +0000526 return query_args
527
528
529 def get_hosts(self, hostnames=(), status=None, label=None, **dargs):
530 query_args = dict(dargs)
531 query_args.update(self._dict_for_host_query(hostnames=hostnames,
532 status=status,
533 label=label))
534 hosts = self.run('get_hosts', **query_args)
535 return [Host(self, h) for h in hosts]
536
537
538 def get_hostnames(self, status=None, label=None, **dargs):
539 """Like get_hosts() but returns hostnames instead of Host objects."""
540 # This implementation can be replaced with a more efficient one
541 # that does not query for entire host objects in the future.
542 return [host_obj.hostname for host_obj in
543 self.get_hosts(status=status, label=label, **dargs)]
544
545
546 def reverify_hosts(self, hostnames=(), status=None, label=None):
547 query_args = dict(locked=False,
548 aclgroup__users__login=self.user)
549 query_args.update(self._dict_for_host_query(hostnames=hostnames,
550 status=status,
551 label=label))
mbligh4e545a52009-12-19 05:30:39 +0000552 return self.run('reverify_hosts', **query_args)
553
554
mbligh67647152008-11-19 00:18:14 +0000555 def create_host(self, hostname, **dargs):
mbligh54459c72009-01-21 19:26:44 +0000556 id = self.run('add_host', hostname=hostname, **dargs)
mbligh67647152008-11-19 00:18:14 +0000557 return self.get_hosts(id=id)[0]
558
559
MK Ryuacf35922014-10-03 14:56:49 -0700560 def get_host_attribute(self, attr, **dargs):
561 host_attrs = self.run('get_host_attribute', attribute=attr, **dargs)
562 return [HostAttribute(self, a) for a in host_attrs]
563
564
Chris Masone8abb6fc2012-01-31 09:27:36 -0800565 def set_host_attribute(self, attr, val, **dargs):
566 self.run('set_host_attribute', attribute=attr, value=val, **dargs)
567
568
mbligh67647152008-11-19 00:18:14 +0000569 def get_labels(self, **dargs):
570 labels = self.run('get_labels', **dargs)
mbligh5280e3b2008-12-22 14:39:28 +0000571 return [Label(self, l) for l in labels]
mbligh67647152008-11-19 00:18:14 +0000572
573
574 def create_label(self, name, **dargs):
mbligh54459c72009-01-21 19:26:44 +0000575 id = self.run('add_label', name=name, **dargs)
mbligh67647152008-11-19 00:18:14 +0000576 return self.get_labels(id=id)[0]
577
578
579 def get_acls(self, **dargs):
580 acls = self.run('get_acl_groups', **dargs)
mbligh5280e3b2008-12-22 14:39:28 +0000581 return [Acl(self, a) for a in acls]
mbligh67647152008-11-19 00:18:14 +0000582
583
584 def create_acl(self, name, **dargs):
mbligh54459c72009-01-21 19:26:44 +0000585 id = self.run('add_acl_group', name=name, **dargs)
mbligh67647152008-11-19 00:18:14 +0000586 return self.get_acls(id=id)[0]
587
588
mbligh54459c72009-01-21 19:26:44 +0000589 def get_users(self, **dargs):
590 users = self.run('get_users', **dargs)
591 return [User(self, u) for u in users]
592
593
mbligh1354c9d2008-12-22 14:56:13 +0000594 def generate_control_file(self, tests, **dargs):
595 ret = self.run('generate_control_file', tests=tests, **dargs)
596 return ControlFile(self, ret)
597
598
mbligh67647152008-11-19 00:18:14 +0000599 def get_jobs(self, summary=False, **dargs):
600 if summary:
601 jobs_data = self.run('get_jobs_summary', **dargs)
602 else:
603 jobs_data = self.run('get_jobs', **dargs)
mblighafbba0c2009-06-08 16:44:45 +0000604 jobs = []
605 for j in jobs_data:
606 job = Job(self, j)
607 # Set up some extra information defaults
608 job.testname = re.sub('\s.*', '', job.name) # arbitrary default
609 job.platform_results = {}
610 job.platform_reasons = {}
611 jobs.append(job)
612 return jobs
mbligh67647152008-11-19 00:18:14 +0000613
614
615 def get_host_queue_entries(self, **data):
616 entries = self.run('get_host_queue_entries', **data)
mblighf9e35862009-02-26 01:03:11 +0000617 job_statuses = [JobStatus(self, e) for e in entries]
mbligh99b24f42009-06-08 16:45:55 +0000618
619 # Sadly, get_host_queue_entries doesn't return platforms, we have
620 # to get those back from an explicit get_hosts queury, then patch
621 # the new host objects back into the host list.
622 hostnames = [s.host.hostname for s in job_statuses if s.host]
623 host_hash = {}
624 for host in self.get_hosts(hostname__in=hostnames):
625 host_hash[host.hostname] = host
626 for status in job_statuses:
627 if status.host:
Fang Deng97dafbc2015-04-23 23:06:18 -0700628 status.host = host_hash.get(status.host.hostname)
mblighf9e35862009-02-26 01:03:11 +0000629 # filter job statuses that have either host or meta_host
630 return [status for status in job_statuses if (status.host or
631 status.meta_host)]
mbligh67647152008-11-19 00:18:14 +0000632
633
MK Ryu1b2d7f92015-02-24 17:45:02 -0800634 def get_special_tasks(self, **data):
635 tasks = self.run('get_special_tasks', **data)
636 return [SpecialTask(self, t) for t in tasks]
637
638
J. Richard Barnette9f10c9f2015-04-13 16:44:50 -0700639 def get_host_special_tasks(self, host_id, **data):
640 tasks = self.run('get_host_special_tasks',
641 host_id=host_id, **data)
642 return [SpecialTask(self, t) for t in tasks]
643
644
J. Richard Barnette8dbd6d32015-05-01 11:01:12 -0700645 def get_host_status_task(self, host_id, end_time):
646 task = self.run('get_host_status_task',
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700647 host_id=host_id, end_time=end_time)
J. Richard Barnettebc9a7952015-04-16 17:43:27 -0700648 return SpecialTask(self, task) if task else None
649
650
J. Richard Barnette8abbfd62015-06-23 12:46:54 -0700651 def get_host_diagnosis_interval(self, host_id, end_time, success):
652 return self.run('get_host_diagnosis_interval',
653 host_id=host_id, end_time=end_time,
654 success=success)
655
656
Allen Li352b86a2016-12-14 12:11:27 -0800657 def create_job(self, control_file, name=' ',
658 priority=priorities.Priority.DEFAULT,
659 control_type=control_data.CONTROL_TYPE_NAMES.CLIENT,
660 **dargs):
mbligh67647152008-11-19 00:18:14 +0000661 id = self.run('create_job', name=name, priority=priority,
662 control_file=control_file, control_type=control_type, **dargs)
663 return self.get_jobs(id=id)[0]
664
665
Simran Basi01984f52015-10-12 15:36:45 -0700666 def abort_jobs(self, jobs):
667 """Abort a list of jobs.
668
669 Already completed jobs will not be affected.
670
671 @param jobs: List of job ids to abort.
672 """
673 for job in jobs:
674 self.run('abort_host_queue_entries', job_id=job)
675
676
Kevin Cheng19521982016-09-22 12:27:23 -0700677 def get_hosts_by_attribute(self, attribute, value):
678 """
679 Get the list of hosts that share the same host attribute value.
680
681 @param attribute: String of the host attribute to check.
682 @param value: String of the value that is shared between hosts.
683
684 @returns List of hostnames that all have the same host attribute and
685 value.
686 """
687 return self.run('get_hosts_by_attribute',
688 attribute=attribute, value=value)
689
690
691 def lock_host(self, host, lock_reason, fail_if_locked=False):
692 """
693 Lock the given host with the given lock reason.
694
695 Locking a host that's already locked using the 'modify_hosts' rpc
696 will raise an exception. That's why fail_if_locked exists so the
697 caller can determine if the lock succeeded or failed. This will
698 save every caller from wrapping lock_host in a try-except.
699
700 @param host: hostname of host to lock.
701 @param lock_reason: Reason for locking host.
702 @param fail_if_locked: Return False if host is already locked.
703
704 @returns Boolean, True if lock was successful, False otherwise.
705 """
706 try:
707 self.run('modify_hosts',
708 host_filter_data={'hostname': host},
709 update_data={'locked': True,
710 'lock_reason': lock_reason})
711 except Exception:
712 return not fail_if_locked
713 return True
714
715
716 def unlock_hosts(self, locked_hosts):
717 """
718 Unlock the hosts.
719
720 Unlocking a host that's already unlocked will do nothing so we don't
721 need any special try-except clause here.
722
723 @param locked_hosts: List of hostnames of hosts to unlock.
724 """
725 self.run('modify_hosts',
726 host_filter_data={'hostname__in': locked_hosts},
727 update_data={'locked': False,
728 'lock_reason': ''})
729
730
mbligh5280e3b2008-12-22 14:39:28 +0000731class TestResults(object):
732 """
733 Container class used to hold the results of the tests for a job
734 """
735 def __init__(self):
736 self.good = []
737 self.fail = []
mbligh451ede12009-02-12 21:54:03 +0000738 self.pending = []
mbligh5280e3b2008-12-22 14:39:28 +0000739
740
741 def add(self, result):
mbligh451ede12009-02-12 21:54:03 +0000742 if result.complete_count > result.pass_count:
743 self.fail.append(result)
744 elif result.incomplete_count > 0:
745 self.pending.append(result)
mbligh5280e3b2008-12-22 14:39:28 +0000746 else:
mbligh451ede12009-02-12 21:54:03 +0000747 self.good.append(result)
mbligh5280e3b2008-12-22 14:39:28 +0000748
749
750class RpcObject(object):
mbligh67647152008-11-19 00:18:14 +0000751 """
752 Generic object used to construct python objects from rpc calls
753 """
754 def __init__(self, afe, hash):
755 self.afe = afe
756 self.hash = hash
757 self.__dict__.update(hash)
758
759
760 def __str__(self):
761 return dump_object(self.__repr__(), self)
762
763
mbligh1354c9d2008-12-22 14:56:13 +0000764class ControlFile(RpcObject):
765 """
766 AFE control file object
767
768 Fields: synch_count, dependencies, control_file, is_server
769 """
770 def __repr__(self):
771 return 'CONTROL FILE: %s' % self.control_file
772
773
mbligh5280e3b2008-12-22 14:39:28 +0000774class Label(RpcObject):
mbligh67647152008-11-19 00:18:14 +0000775 """
776 AFE label object
777
778 Fields:
779 name, invalid, platform, kernel_config, id, only_if_needed
780 """
781 def __repr__(self):
782 return 'LABEL: %s' % self.name
783
784
785 def add_hosts(self, hosts):
Chris Masone3a560bd2011-11-14 16:53:56 -0800786 return self.afe.run('label_add_hosts', id=self.id, hosts=hosts)
mbligh67647152008-11-19 00:18:14 +0000787
788
789 def remove_hosts(self, hosts):
Chris Masone3a560bd2011-11-14 16:53:56 -0800790 return self.afe.run('label_remove_hosts', id=self.id, hosts=hosts)
mbligh67647152008-11-19 00:18:14 +0000791
792
mbligh5280e3b2008-12-22 14:39:28 +0000793class Acl(RpcObject):
mbligh67647152008-11-19 00:18:14 +0000794 """
795 AFE acl object
796
797 Fields:
798 users, hosts, description, name, id
799 """
800 def __repr__(self):
801 return 'ACL: %s' % self.name
802
803
804 def add_hosts(self, hosts):
805 self.afe.log('Adding hosts %s to ACL %s' % (hosts, self.name))
806 return self.afe.run('acl_group_add_hosts', self.id, hosts)
807
808
809 def remove_hosts(self, hosts):
810 self.afe.log('Removing hosts %s from ACL %s' % (hosts, self.name))
811 return self.afe.run('acl_group_remove_hosts', self.id, hosts)
812
813
mbligh54459c72009-01-21 19:26:44 +0000814 def add_users(self, users):
815 self.afe.log('Adding users %s to ACL %s' % (users, self.name))
816 return self.afe.run('acl_group_add_users', id=self.name, users=users)
817
818
mbligh5280e3b2008-12-22 14:39:28 +0000819class Job(RpcObject):
mbligh67647152008-11-19 00:18:14 +0000820 """
821 AFE job object
822
823 Fields:
824 name, control_file, control_type, synch_count, reboot_before,
825 run_verify, priority, email_list, created_on, dependencies,
826 timeout, owner, reboot_after, id
827 """
828 def __repr__(self):
829 return 'JOB: %s' % self.id
830
831
mbligh5280e3b2008-12-22 14:39:28 +0000832class JobStatus(RpcObject):
mbligh67647152008-11-19 00:18:14 +0000833 """
834 AFE job_status object
835
836 Fields:
837 status, complete, deleted, meta_host, host, active, execution_subdir, id
838 """
839 def __init__(self, afe, hash):
MK Ryu1b2d7f92015-02-24 17:45:02 -0800840 super(JobStatus, self).__init__(afe, hash)
mbligh5280e3b2008-12-22 14:39:28 +0000841 self.job = Job(afe, self.job)
Dale Curtis8adf7892011-09-08 16:13:36 -0700842 if getattr(self, 'host'):
mbligh99b24f42009-06-08 16:45:55 +0000843 self.host = Host(afe, self.host)
mbligh67647152008-11-19 00:18:14 +0000844
845
846 def __repr__(self):
mbligh451ede12009-02-12 21:54:03 +0000847 if self.host and self.host.hostname:
848 hostname = self.host.hostname
849 else:
850 hostname = 'None'
851 return 'JOB STATUS: %s-%s' % (self.job.id, hostname)
mbligh67647152008-11-19 00:18:14 +0000852
853
MK Ryu1b2d7f92015-02-24 17:45:02 -0800854class SpecialTask(RpcObject):
855 """
856 AFE special task object
857 """
858 def __init__(self, afe, hash):
859 super(SpecialTask, self).__init__(afe, hash)
860 self.host = Host(afe, self.host)
861
862
863 def __repr__(self):
864 return 'SPECIAL TASK: %s' % self.id
865
866
mbligh5280e3b2008-12-22 14:39:28 +0000867class Host(RpcObject):
mbligh67647152008-11-19 00:18:14 +0000868 """
869 AFE host object
870
871 Fields:
872 status, lock_time, locked_by, locked, hostname, invalid,
873 synch_id, labels, platform, protection, dirty, id
874 """
875 def __repr__(self):
876 return 'HOST OBJECT: %s' % self.hostname
877
878
879 def show(self):
880 labels = list(set(self.labels) - set([self.platform]))
881 print '%-6s %-7s %-7s %-16s %s' % (self.hostname, self.status,
882 self.locked, self.platform,
883 ', '.join(labels))
884
885
mbligh54459c72009-01-21 19:26:44 +0000886 def delete(self):
887 return self.afe.run('delete_host', id=self.id)
888
889
mbligh6463c4b2009-01-30 00:33:37 +0000890 def modify(self, **dargs):
891 return self.afe.run('modify_host', id=self.id, **dargs)
892
893
mbligh67647152008-11-19 00:18:14 +0000894 def get_acls(self):
895 return self.afe.get_acls(hosts__hostname=self.hostname)
896
897
898 def add_acl(self, acl_name):
899 self.afe.log('Adding ACL %s to host %s' % (acl_name, self.hostname))
900 return self.afe.run('acl_group_add_hosts', id=acl_name,
901 hosts=[self.hostname])
902
903
904 def remove_acl(self, acl_name):
905 self.afe.log('Removing ACL %s from host %s' % (acl_name, self.hostname))
906 return self.afe.run('acl_group_remove_hosts', id=acl_name,
907 hosts=[self.hostname])
908
909
910 def get_labels(self):
911 return self.afe.get_labels(host__hostname__in=[self.hostname])
912
913
914 def add_labels(self, labels):
915 self.afe.log('Adding labels %s to host %s' % (labels, self.hostname))
916 return self.afe.run('host_add_labels', id=self.id, labels=labels)
917
918
919 def remove_labels(self, labels):
920 self.afe.log('Removing labels %s from host %s' % (labels,self.hostname))
921 return self.afe.run('host_remove_labels', id=self.id, labels=labels)
mbligh5b618382008-12-03 15:24:01 +0000922
923
mbligh54459c72009-01-21 19:26:44 +0000924class User(RpcObject):
925 def __repr__(self):
926 return 'USER: %s' % self.login
927
928
mbligh5280e3b2008-12-22 14:39:28 +0000929class TestStatus(RpcObject):
mblighc31e4022008-12-11 19:32:30 +0000930 """
931 TKO test status object
932
933 Fields:
934 test_idx, hostname, testname, id
935 complete_count, incomplete_count, group_count, pass_count
936 """
937 def __repr__(self):
938 return 'TEST STATUS: %s' % self.id
939
940
MK Ryuacf35922014-10-03 14:56:49 -0700941class HostAttribute(RpcObject):
942 """
943 AFE host attribute object
944
945 Fields:
946 id, host, attribute, value
947 """
948 def __repr__(self):
949 return 'HOST ATTRIBUTE %d' % self.id