blob: 4e8626f06c5c1ff6a41bc64fc21d56718d2c816f [file] [log] [blame]
Dan Shia1ecd5c2013-06-06 11:21:31 -07001# Copyright (c) 2013 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
Dan Shia1ecd5c2013-06-06 11:21:31 -07005
Paul Hobbs20cc72a2016-08-30 16:57:05 -07006import contextlib
Fang Deng18699fe2015-12-04 16:40:27 -08007import grp
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -08008import httplib
9import json
Alex Millerdadc2c22013-07-08 15:21:21 -070010import logging
MK Ryu35d661e2014-09-25 17:44:10 -070011import os
beeps023afc62014-02-04 16:59:22 -080012import random
Alex Millerdadc2c22013-07-08 15:21:21 -070013import re
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -080014import time
Paul Drewsbef578d2013-09-24 15:10:36 -070015import urllib2
Alex Millerdadc2c22013-07-08 15:21:21 -070016
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -080017import common
Dan Shief31f032016-05-13 15:51:39 -070018from autotest_lib.client.common_lib import utils
beeps023afc62014-02-04 16:59:22 -080019from autotest_lib.client.common_lib import error
20from autotest_lib.client.common_lib import global_config
MK Ryu0c1a37d2015-04-30 12:00:55 -070021from autotest_lib.client.common_lib import host_queue_entry_states
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -070022from autotest_lib.client.common_lib import host_states
Simran Basi7756a0b2016-03-16 13:10:07 -070023from autotest_lib.server.cros import provision
Dan Shia1ecd5c2013-06-06 11:21:31 -070024from autotest_lib.server.cros.dynamic_suite import constants
Dan Shi7e04fa82013-07-25 15:08:48 -070025from autotest_lib.server.cros.dynamic_suite import job_status
Dan Shia1ecd5c2013-06-06 11:21:31 -070026
27
Dan Shid37736b2016-07-06 15:10:29 -070028CONFIG = global_config.global_config
29
30_SHERIFF_JS = CONFIG.get_config_value('NOTIFICATIONS', 'sheriffs', default='')
31_LAB_SHERIFF_JS = CONFIG.get_config_value(
32 'NOTIFICATIONS', 'lab_sheriffs', default='')
33_CHROMIUM_BUILD_URL = CONFIG.get_config_value(
34 'NOTIFICATIONS', 'chromium_build_url', default='')
Alex Millerdadc2c22013-07-08 15:21:21 -070035
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -080036LAB_GOOD_STATES = ('open', 'throttled')
37
Dan Shid37736b2016-07-06 15:10:29 -070038ENABLE_DRONE_IN_RESTRICTED_SUBNET = CONFIG.get_config_value(
39 'CROS', 'enable_drone_in_restricted_subnet', type=bool,
40 default=False)
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -080041
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -070042# Wait at most 10 mins for duts to go idle.
43IDLE_DUT_WAIT_TIMEOUT = 600
44
Dan Shi43274402016-11-04 15:13:43 -070045# Mapping between board name and build target. This is for special case handling
46# for certain Android board that the board name and build target name does not
47# match.
tturney08fc62e2016-11-17 15:44:30 -080048ANDROID_TARGET_TO_BOARD_MAP = {
49 'seed_l8150': 'gm4g_sprout',
50 'bat_land': 'bat'
51 }
52ANDROID_BOARD_TO_TARGET_MAP = {
53 'gm4g_sprout': 'seed_l8150',
54 'bat': 'bat_land'
55 }
Dan Shi43274402016-11-04 15:13:43 -070056
J. Richard Barnetteabbe0962013-12-10 18:15:44 -080057class TestLabException(Exception):
58 """Exception raised when the Test Lab blocks a test or suite."""
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -080059 pass
60
61
62class ParseBuildNameException(Exception):
63 """Raised when ParseBuildName() cannot parse a build name."""
64 pass
65
66
Fang Dengf08814a2015-08-03 18:12:18 +000067class Singleton(type):
68 """Enforce that only one client class is instantiated per process."""
69 _instances = {}
70
71 def __call__(cls, *args, **kwargs):
72 """Fetch the instance of a class to use for subsequent calls."""
73 if cls not in cls._instances:
74 cls._instances[cls] = super(Singleton, cls).__call__(
75 *args, **kwargs)
76 return cls._instances[cls]
77
Kevin Cheng05ae2a42016-06-06 10:12:48 -070078class EmptyAFEHost(object):
79 """Object to represent an AFE host object when there is no AFE."""
80
81 def __init__(self):
82 """
83 We'll be setting the instance attributes as we use them. Right now
84 we only use attributes and labels but as time goes by and other
85 attributes are used from an actual AFE Host object (check
86 rpc_interfaces.get_hosts()), we'll add them in here so users won't be
87 perplexed why their host's afe_host object complains that attribute
88 doesn't exist.
89 """
90 self.attributes = {}
91 self.labels = []
92
Fang Dengf08814a2015-08-03 18:12:18 +000093
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -080094def ParseBuildName(name):
95 """Format a build name, given board, type, milestone, and manifest num.
96
Simran Basib7d21162014-05-21 15:26:16 -070097 @param name: a build name, e.g. 'x86-alex-release/R20-2015.0.0' or a
98 relative build name, e.g. 'x86-alex-release/LATEST'
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -080099
100 @return board: board the manifest is for, e.g. x86-alex.
101 @return type: one of 'release', 'factory', or 'firmware'
102 @return milestone: (numeric) milestone the manifest was associated with.
Simran Basib7d21162014-05-21 15:26:16 -0700103 Will be None for relative build names.
104 @return manifest: manifest number, e.g. '2015.0.0'.
105 Will be None for relative build names.
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800106
107 """
Dan Shie02810d2016-08-25 09:44:57 -0700108 match = re.match(r'(trybot-)?(?P<board>[\w-]+?)(?:-chrome)?(?:-chromium)?'
109 r'-(?P<type>\w+)/(R(?P<milestone>\d+)-'
110 r'(?P<manifest>[\d.ab-]+)|LATEST)',
Simran Basif8f648e2014-09-09 11:40:03 -0700111 name)
112 if match and len(match.groups()) >= 5:
Simran Basib7d21162014-05-21 15:26:16 -0700113 return (match.group('board'), match.group('type'),
114 match.group('milestone'), match.group('manifest'))
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800115 raise ParseBuildNameException('%s is a malformed build name.' % name)
116
Alex Millerdadc2c22013-07-08 15:21:21 -0700117
Dan Shi3d7a0e12015-10-12 11:55:45 -0700118def get_labels_from_afe(hostname, label_prefix, afe):
119 """Retrieve a host's specific labels from the AFE.
120
121 Looks for the host labels that have the form <label_prefix>:<value>
122 and returns the "<value>" part of the label. None is returned
123 if there is not a label matching the pattern
124
125 @param hostname: hostname of given DUT.
126 @param label_prefix: prefix of label to be matched, e.g., |board:|
127 @param afe: afe instance.
128
129 @returns A list of labels that match the prefix or 'None'
130
131 """
132 labels = afe.get_labels(name__startswith=label_prefix,
133 host__hostname__in=[hostname])
134 if labels:
135 return [l.name.split(label_prefix, 1)[1] for l in labels]
136
137
Dan Shia1ecd5c2013-06-06 11:21:31 -0700138def get_label_from_afe(hostname, label_prefix, afe):
139 """Retrieve a host's specific label from the AFE.
140
141 Looks for a host label that has the form <label_prefix>:<value>
142 and returns the "<value>" part of the label. None is returned
143 if there is not a label matching the pattern
144
145 @param hostname: hostname of given DUT.
146 @param label_prefix: prefix of label to be matched, e.g., |board:|
147 @param afe: afe instance.
148 @returns the label that matches the prefix or 'None'
149
150 """
Dan Shi3d7a0e12015-10-12 11:55:45 -0700151 labels = get_labels_from_afe(hostname, label_prefix, afe)
Dan Shia1ecd5c2013-06-06 11:21:31 -0700152 if labels and len(labels) == 1:
Dan Shi3d7a0e12015-10-12 11:55:45 -0700153 return labels[0]
Dan Shia1ecd5c2013-06-06 11:21:31 -0700154
155
156def get_board_from_afe(hostname, afe):
157 """Retrieve given host's board from its labels in the AFE.
158
159 Looks for a host label of the form "board:<board>", and
160 returns the "<board>" part of the label. `None` is returned
161 if there is not a single, unique label matching the pattern.
162
163 @param hostname: hostname of given DUT.
164 @param afe: afe instance.
165 @returns board from label, or `None`.
166
167 """
168 return get_label_from_afe(hostname, constants.BOARD_PREFIX, afe)
169
170
171def get_build_from_afe(hostname, afe):
172 """Retrieve the current build for given host from the AFE.
173
174 Looks through the host's labels in the AFE to determine its build.
175
176 @param hostname: hostname of given DUT.
177 @param afe: afe instance.
178 @returns The current build or None if it could not find it or if there
179 were multiple build labels assigned to this host.
180
181 """
Simran Basi7756a0b2016-03-16 13:10:07 -0700182 for prefix in [provision.CROS_VERSION_PREFIX,
183 provision.ANDROID_BUILD_VERSION_PREFIX]:
184 build = get_label_from_afe(hostname, prefix + ':', afe)
185 if build:
186 return build
187 return None
Dan Shia1ecd5c2013-06-06 11:21:31 -0700188
189
Allen Li6a612392016-08-18 12:09:32 -0700190# TODO(fdeng): fix get_sheriffs crbug.com/483254
Fang Deng3197b392013-06-26 11:42:02 -0700191def get_sheriffs(lab_only=False):
Alex Millerdadc2c22013-07-08 15:21:21 -0700192 """
193 Polls the javascript file that holds the identity of the sheriff and
194 parses it's output to return a list of chromium sheriff email addresses.
195 The javascript file can contain the ldap of more than one sheriff, eg:
196 document.write('sheriff_one, sheriff_two').
197
Fang Deng3197b392013-06-26 11:42:02 -0700198 @param lab_only: if True, only pulls lab sheriff.
199 @return: A list of chroium.org sheriff email addresses to cc on the bug.
200 An empty list if failed to parse the javascript.
Alex Millerdadc2c22013-07-08 15:21:21 -0700201 """
202 sheriff_ids = []
Fang Deng3197b392013-06-26 11:42:02 -0700203 sheriff_js_list = _LAB_SHERIFF_JS.split(',')
204 if not lab_only:
205 sheriff_js_list.extend(_SHERIFF_JS.split(','))
206
207 for sheriff_js in sheriff_js_list:
Alex Millerdadc2c22013-07-08 15:21:21 -0700208 try:
Dan Shief31f032016-05-13 15:51:39 -0700209 url_content = utils.urlopen('%s%s'% (
Alex Millerdadc2c22013-07-08 15:21:21 -0700210 _CHROMIUM_BUILD_URL, sheriff_js)).read()
211 except (ValueError, IOError) as e:
beeps4efdf032013-09-17 11:27:14 -0700212 logging.warning('could not parse sheriff from url %s%s: %s',
213 _CHROMIUM_BUILD_URL, sheriff_js, str(e))
Paul Drewsbef578d2013-09-24 15:10:36 -0700214 except (urllib2.URLError, httplib.HTTPException) as e:
215 logging.warning('unexpected error reading from url "%s%s": %s',
216 _CHROMIUM_BUILD_URL, sheriff_js, str(e))
Alex Millerdadc2c22013-07-08 15:21:21 -0700217 else:
218 ldaps = re.search(r"document.write\('(.*)'\)", url_content)
219 if not ldaps:
beeps4efdf032013-09-17 11:27:14 -0700220 logging.warning('Could not retrieve sheriff ldaps for: %s',
221 url_content)
Alex Millerdadc2c22013-07-08 15:21:21 -0700222 continue
223 sheriff_ids += ['%s@chromium.org' % alias.replace(' ', '')
224 for alias in ldaps.group(1).split(',')]
225 return sheriff_ids
beeps46dadc92013-11-07 14:07:10 -0800226
227
228def remote_wget(source_url, dest_path, ssh_cmd):
229 """wget source_url from localhost to dest_path on remote host using ssh.
230
231 @param source_url: The complete url of the source of the package to send.
232 @param dest_path: The path on the remote host's file system where we would
233 like to store the package.
234 @param ssh_cmd: The ssh command to use in performing the remote wget.
235 """
236 wget_cmd = ("wget -O - %s | %s 'cat >%s'" %
237 (source_url, ssh_cmd, dest_path))
Dan Shief31f032016-05-13 15:51:39 -0700238 utils.run(wget_cmd)
beeps46dadc92013-11-07 14:07:10 -0800239
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800240
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800241_MAX_LAB_STATUS_ATTEMPTS = 5
242def _get_lab_status(status_url):
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800243 """Grabs the current lab status and message.
244
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800245 @returns The JSON object obtained from the given URL.
246
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800247 """
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800248 retry_waittime = 1
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800249 for _ in range(_MAX_LAB_STATUS_ATTEMPTS):
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800250 try:
251 response = urllib2.urlopen(status_url)
252 except IOError as e:
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800253 logging.debug('Error occurred when grabbing the lab status: %s.',
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800254 e)
255 time.sleep(retry_waittime)
256 continue
257 # Check for successful response code.
258 if response.getcode() == 200:
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800259 return json.load(response)
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800260 time.sleep(retry_waittime)
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800261 return None
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800262
263
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800264def _decode_lab_status(lab_status, build):
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800265 """Decode lab status, and report exceptions as needed.
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800266
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800267 Take a deserialized JSON object from the lab status page, and
268 interpret it to determine the actual lab status. Raise
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800269 exceptions as required to report when the lab is down.
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800270
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800271 @param build: build name that we want to check the status of.
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800272
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800273 @raises TestLabException Raised if a request to test for the given
274 status and build should be blocked.
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800275 """
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800276 # First check if the lab is up.
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800277 if not lab_status['general_state'] in LAB_GOOD_STATES:
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800278 raise TestLabException('Chromium OS Test Lab is closed: '
279 '%s.' % lab_status['message'])
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800280
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800281 # Check if the build we wish to use is disabled.
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800282 # Lab messages should be in the format of:
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800283 # Lab is 'status' [regex ...] (comment)
284 # If the build name matches any regex, it will be blocked.
285 build_exceptions = re.search('\[(.*)\]', lab_status['message'])
Prashanth Balasubramanianae437212014-10-27 11:17:26 -0700286 if not build_exceptions or not build:
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800287 return
288 for build_pattern in build_exceptions.group(1).split():
J. Richard Barnette7f215d32015-06-19 12:44:38 -0700289 if re.match(build_pattern, build):
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800290 raise TestLabException('Chromium OS Test Lab is closed: '
291 '%s matches %s.' % (
292 build, build_pattern))
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800293 return
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800294
295
Dan Shi94234cb2014-05-23 20:04:31 -0700296def is_in_lab():
297 """Check if current Autotest instance is in lab
298
299 @return: True if the Autotest instance is in lab.
300 """
Dan Shid37736b2016-07-06 15:10:29 -0700301 test_server_name = CONFIG.get_config_value('SERVER', 'hostname')
Dan Shi94234cb2014-05-23 20:04:31 -0700302 return test_server_name.startswith('cautotest')
303
304
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800305def check_lab_status(build):
306 """Check if the lab status allows us to schedule for a build.
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800307
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800308 Checks if the lab is down, or if testing for the requested build
309 should be blocked.
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800310
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800311 @param build: Name of the build to be scheduled for testing.
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800312
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800313 @raises TestLabException Raised if a request to test for the given
314 status and build should be blocked.
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800315
316 """
317 # Ensure we are trying to schedule on the actual lab.
Dan Shi94234cb2014-05-23 20:04:31 -0700318 if not is_in_lab():
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800319 return
320
321 # Download the lab status from its home on the web.
Dan Shid37736b2016-07-06 15:10:29 -0700322 status_url = CONFIG.get_config_value('CROS', 'lab_status_url')
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800323 json_status = _get_lab_status(status_url)
324 if json_status is None:
325 # We go ahead and say the lab is open if we can't get the status.
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700326 logging.warning('Could not get a status from %s', status_url)
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800327 return
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800328 _decode_lab_status(json_status, build)
beeps023afc62014-02-04 16:59:22 -0800329
330
331def lock_host_with_labels(afe, lock_manager, labels):
332 """Lookup and lock one host that matches the list of input labels.
333
334 @param afe: An instance of the afe class, as defined in server.frontend.
335 @param lock_manager: A lock manager capable of locking hosts, eg the
336 one defined in server.cros.host_lock_manager.
337 @param labels: A list of labels to look for on hosts.
338
339 @return: The hostname of a host matching all labels, and locked through the
340 lock_manager. The hostname will be as specified in the database the afe
341 object is associated with, i.e if it exists in afe_hosts with a .cros
342 suffix, the hostname returned will contain a .cros suffix.
343
344 @raises: error.NoEligibleHostException: If no hosts matching the list of
345 input labels are available.
346 @raises: error.TestError: If unable to lock a host matching the labels.
347 """
348 potential_hosts = afe.get_hosts(multiple_labels=labels)
349 if not potential_hosts:
350 raise error.NoEligibleHostException(
351 'No devices found with labels %s.' % labels)
352
353 # This prevents errors where a fault might seem repeatable
354 # because we lock, say, the same packet capturer for each test run.
355 random.shuffle(potential_hosts)
356 for host in potential_hosts:
357 if lock_manager.lock([host.hostname]):
358 logging.info('Locked device %s with labels %s.',
359 host.hostname, labels)
360 return host.hostname
361 else:
362 logging.info('Unable to lock device %s with labels %s.',
363 host.hostname, labels)
364
365 raise error.TestError('Could not lock a device with labels %s' % labels)
Dan Shi7e04fa82013-07-25 15:08:48 -0700366
367
368def get_test_views_from_tko(suite_job_id, tko):
369 """Get test name and result for given suite job ID.
370
371 @param suite_job_id: ID of suite job.
372 @param tko: an instance of TKO as defined in server/frontend.py.
373 @return: A dictionary of test status keyed by test name, e.g.,
374 {'dummy_Fail.Error': 'ERROR', 'dummy_Fail.NAError': 'TEST_NA'}
375 @raise: Exception when there is no test view found.
376
377 """
378 views = tko.run('get_detailed_test_views', afe_job_id=suite_job_id)
379 relevant_views = filter(job_status.view_is_relevant, views)
380 if not relevant_views:
381 raise Exception('Failed to retrieve job results.')
382
383 test_views = {}
384 for view in relevant_views:
385 test_views[view['test_name']] = view['status']
386
387 return test_views
MK Ryu35d661e2014-09-25 17:44:10 -0700388
389
MK Ryuc9c0c3f2014-10-27 14:36:01 -0700390def get_data_key(prefix, suite, build, board):
391 """
392 Constructs a key string from parameters.
393
394 @param prefix: Prefix for the generating key.
395 @param suite: a suite name. e.g., bvt-cq, bvt-inline, dummy
396 @param build: The build string. This string should have a consistent
397 format eg: x86-mario-release/R26-3570.0.0. If the format of this
398 string changes such that we can't determine build_type or branch
399 we give up and use the parametes we're sure of instead (suite,
400 board). eg:
401 1. build = x86-alex-pgo-release/R26-3570.0.0
402 branch = 26
403 build_type = pgo-release
404 2. build = lumpy-paladin/R28-3993.0.0-rc5
405 branch = 28
406 build_type = paladin
407 @param board: The board that this suite ran on.
408 @return: The key string used for a dictionary.
409 """
410 try:
411 _board, build_type, branch = ParseBuildName(build)[:3]
412 except ParseBuildNameException as e:
413 logging.error(str(e))
414 branch = 'Unknown'
415 build_type = 'Unknown'
416 else:
417 embedded_str = re.search(r'x86-\w+-(.*)', _board)
418 if embedded_str:
419 build_type = embedded_str.group(1) + '-' + build_type
420
421 data_key_dict = {
422 'prefix': prefix,
423 'board': board,
424 'branch': branch,
425 'build_type': build_type,
426 'suite': suite,
427 }
428 return ('%(prefix)s.%(board)s.%(build_type)s.%(branch)s.%(suite)s'
429 % data_key_dict)
MK Ryu83184352014-12-10 14:59:40 -0800430
431
MK Ryu2d0a3642015-01-07 15:11:19 -0800432def setup_logging(logfile=None, prefix=False):
MK Ryu83184352014-12-10 14:59:40 -0800433 """Setup basic logging with all logging info stripped.
434
435 Calls to logging will only show the message. No severity is logged.
436
437 @param logfile: If specified dump output to a file as well.
MK Ryu2d0a3642015-01-07 15:11:19 -0800438 @param prefix: Flag for log prefix. Set to True to add prefix to log
439 entries to include timestamp and log level. Default is False.
MK Ryu83184352014-12-10 14:59:40 -0800440 """
441 # Remove all existing handlers. client/common_lib/logging_config adds
442 # a StreamHandler to logger when modules are imported, e.g.,
443 # autotest_lib.client.bin.utils. A new StreamHandler will be added here to
444 # log only messages, not severity.
445 logging.getLogger().handlers = []
446
MK Ryu2d0a3642015-01-07 15:11:19 -0800447 if prefix:
448 log_format = '%(asctime)s %(levelname)-5s| %(message)s'
449 else:
450 log_format = '%(message)s'
451
MK Ryu83184352014-12-10 14:59:40 -0800452 screen_handler = logging.StreamHandler()
MK Ryu2d0a3642015-01-07 15:11:19 -0800453 screen_handler.setFormatter(logging.Formatter(log_format))
MK Ryu83184352014-12-10 14:59:40 -0800454 logging.getLogger().addHandler(screen_handler)
455 logging.getLogger().setLevel(logging.INFO)
456 if logfile:
457 file_handler = logging.FileHandler(logfile)
MK Ryu2d0a3642015-01-07 15:11:19 -0800458 file_handler.setFormatter(logging.Formatter(log_format))
MK Ryu83184352014-12-10 14:59:40 -0800459 file_handler.setLevel(logging.DEBUG)
460 logging.getLogger().addHandler(file_handler)
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -0800461
462
463def is_shard():
464 """Determines if this instance is running as a shard.
465
466 Reads the global_config value shard_hostname in the section SHARD.
467
468 @return True, if shard_hostname is set, False otherwise.
469 """
Dan Shid37736b2016-07-06 15:10:29 -0700470 hostname = CONFIG.get_config_value('SHARD', 'shard_hostname', default=None)
MK Ryu0c1a37d2015-04-30 12:00:55 -0700471 return bool(hostname)
472
473
Fang Deng0cb2a3b2015-12-10 17:59:00 -0800474def get_global_afe_hostname():
475 """Read the hostname of the global AFE from the global configuration."""
Dan Shid37736b2016-07-06 15:10:29 -0700476 return CONFIG.get_config_value('SERVER', 'global_afe_hostname')
Fang Deng0cb2a3b2015-12-10 17:59:00 -0800477
478
Fang Deng18699fe2015-12-04 16:40:27 -0800479def is_restricted_user(username):
480 """Determines if a user is in a restricted group.
481
482 User in restricted group only have access to master.
483
484 @param username: A string, representing a username.
485
486 @returns: True if the user is in a restricted group.
487 """
488 if not username:
489 return False
490
Dan Shid37736b2016-07-06 15:10:29 -0700491 restricted_groups = CONFIG.get_config_value(
Fang Deng18699fe2015-12-04 16:40:27 -0800492 'AUTOTEST_WEB', 'restricted_groups', default='').split(',')
493 for group in restricted_groups:
Fang Deng5229c852016-02-09 13:30:31 -0800494 try:
495 if group and username in grp.getgrnam(group).gr_mem:
496 return True
497 except KeyError as e:
498 logging.debug("%s is not a valid group.", group)
Fang Deng18699fe2015-12-04 16:40:27 -0800499 return False
500
501
MK Ryu0c1a37d2015-04-30 12:00:55 -0700502def get_special_task_status(is_complete, success, is_active):
503 """Get the status of a special task.
504
505 Emulate a host queue entry status for a special task
506 Although SpecialTasks are not HostQueueEntries, it is helpful to
507 the user to present similar statuses.
508
509 @param is_complete Boolean if the task is completed.
510 @param success Boolean if the task succeeded.
511 @param is_active Boolean if the task is active.
512
513 @return The status of a special task.
514 """
515 if is_complete:
516 if success:
517 return host_queue_entry_states.Status.COMPLETED
518 return host_queue_entry_states.Status.FAILED
519 if is_active:
520 return host_queue_entry_states.Status.RUNNING
521 return host_queue_entry_states.Status.QUEUED
522
523
524def get_special_task_exec_path(hostname, task_id, task_name, time_requested):
525 """Get the execution path of the SpecialTask.
526
527 This method returns different paths depending on where a
528 the task ran:
529 * Master: hosts/hostname/task_id-task_type
530 * Shard: Master_path/time_created
531 This is to work around the fact that a shard can fail independent
532 of the master, and be replaced by another shard that has the same
533 hosts. Without the time_created stamp the logs of the tasks running
534 on the second shard will clobber the logs from the first in google
535 storage, because task ids are not globally unique.
536
537 @param hostname Hostname
538 @param task_id Special task id
539 @param task_name Special task name (e.g., Verify, Repair, etc)
540 @param time_requested Special task requested time.
541
542 @return An execution path for the task.
543 """
544 results_path = 'hosts/%s/%s-%s' % (hostname, task_id, task_name.lower())
545
546 # If we do this on the master it will break backward compatibility,
547 # as there are tasks that currently don't have timestamps. If a host
548 # or job has been sent to a shard, the rpc for that host/job will
549 # be redirected to the shard, so this global_config check will happen
550 # on the shard the logs are on.
551 if not is_shard():
552 return results_path
553
554 # Generate a uid to disambiguate special task result directories
555 # in case this shard fails. The simplest uid is the job_id, however
556 # in rare cases tasks do not have jobs associated with them (eg:
557 # frontend verify), so just use the creation timestamp. The clocks
558 # between a shard and master should always be in sync. Any discrepancies
559 # will be brought to our attention in the form of job timeouts.
560 uid = time_requested.strftime('%Y%d%m%H%M%S')
561
562 # TODO: This is a hack, however it is the easiest way to achieve
563 # correctness. There is currently some debate over the future of
564 # tasks in our infrastructure and refactoring everything right
565 # now isn't worth the time.
566 return '%s/%s' % (results_path, uid)
567
568
569def get_job_tag(id, owner):
570 """Returns a string tag for a job.
571
572 @param id Job id
573 @param owner Job owner
574
575 """
576 return '%s-%s' % (id, owner)
577
578
579def get_hqe_exec_path(tag, execution_subdir):
580 """Returns a execution path to a HQE's results.
581
582 @param tag Tag string for a job associated with a HQE.
583 @param execution_subdir Execution sub-directory string of a HQE.
584
585 """
586 return os.path.join(tag, execution_subdir)
Dan Shi82997b92015-05-06 12:08:02 -0700587
588
589def is_inside_chroot():
590 """Check if the process is running inside chroot.
591
592 This is a wrapper around chromite.lib.cros_build_lib.IsInsideChroot(). The
593 method checks if cros_build_lib can be imported first.
594
595 @return: True if the process is running inside chroot or cros_build_lib
596 cannot be imported.
597
598 """
Prathmesh Prabhu16b46f82017-07-05 12:59:27 -0700599 try:
600 # TODO(crbug.com/739466) This module import is delayed because it adds
601 # 1-2 seconds to the module import time and most users of site_utils
602 # don't need it. The correct fix is to break apart site_utils into more
603 # meaningful chunks.
604 from chromite.lib import cros_build_lib
605 except ImportError:
606 logging.warn('Unable to import chromite. Can not detect chroot. '
607 'Defaulting to False')
608 return False
609 return cros_build_lib.IsInsideChroot()
Dan Shi70647ca2015-07-16 22:52:35 -0700610
611
612def parse_job_name(name):
613 """Parse job name to get information including build, board and suite etc.
614
615 Suite job created by run_suite follows the naming convention of:
616 [build]-test_suites/control.[suite]
617 For example: lumpy-release/R46-7272.0.0-test_suites/control.bvt
Allen Licdd00f22017-02-01 18:01:52 -0800618 The naming convention is defined in rpc_interface.create_suite_job.
Dan Shi70647ca2015-07-16 22:52:35 -0700619
620 Test job created by suite job follows the naming convention of:
621 [build]/[suite]/[test name]
622 For example: lumpy-release/R46-7272.0.0/bvt/login_LoginSuccess
623 The naming convention is defined in
624 server/cros/dynamic_suite/tools.create_job_name
625
626 Note that pgo and chrome-perf builds will fail the method. Since lab does
627 not run test for these builds, they can be ignored.
Dan Shief31f032016-05-13 15:51:39 -0700628 Also, tests for Launch Control builds have different naming convention.
629 The build ID will be used as build_version.
Dan Shi70647ca2015-07-16 22:52:35 -0700630
631 @param name: Name of the job.
632
633 @return: A dictionary containing the test information. The keyvals include:
634 build: Name of the build, e.g., lumpy-release/R46-7272.0.0
635 build_version: The version of the build, e.g., R46-7272.0.0
636 board: Name of the board, e.g., lumpy
637 suite: Name of the test suite, e.g., bvt
638
639 """
640 info = {}
Dan Shief31f032016-05-13 15:51:39 -0700641 suite_job_regex = '([^/]*/[^/]*(?:/\d+)?)-test_suites/control\.(.*)'
642 test_job_regex = '([^/]*/[^/]*(?:/\d+)?)/([^/]+)/.*'
Dan Shi70647ca2015-07-16 22:52:35 -0700643 match = re.match(suite_job_regex, name)
644 if not match:
645 match = re.match(test_job_regex, name)
646 if match:
647 info['build'] = match.groups()[0]
648 info['suite'] = match.groups()[1]
649 info['build_version'] = info['build'].split('/')[1]
650 try:
651 info['board'], _, _, _ = ParseBuildName(info['build'])
652 except ParseBuildNameException:
Dan Shief31f032016-05-13 15:51:39 -0700653 # Try to parse it as Launch Control build
654 # Launch Control builds have name format:
655 # branch/build_target-build_type/build_id.
656 try:
657 _, target, build_id = utils.parse_launch_control_build(
658 info['build'])
659 build_target, _ = utils.parse_launch_control_target(target)
660 if build_target:
661 info['board'] = build_target
662 info['build_version'] = build_id
663 except ValueError:
664 pass
Dan Shi70647ca2015-07-16 22:52:35 -0700665 return info
Kevin Cheng3a4a57a2015-09-30 12:09:50 -0700666
667
668def add_label_detector(label_function_list, label_list=None, label=None):
669 """Decorator used to group functions together into the provided list.
670
671 This is a helper function to automatically add label functions that have
672 the label decorator. This is to help populate the class list of label
673 functions to be retrieved by the get_labels class method.
674
675 @param label_function_list: List of label detecting functions to add
676 decorated function to.
677 @param label_list: List of detectable labels to add detectable labels to.
678 (Default: None)
679 @param label: Label string that is detectable by this detection function
680 (Default: None)
681 """
682 def add_func(func):
683 """
684 @param func: The function to be added as a detector.
685 """
686 label_function_list.append(func)
687 if label and label_list is not None:
688 label_list.append(label)
689 return func
690 return add_func
Simran Basi9f364a62015-12-07 14:15:19 -0800691
692
693def verify_not_root_user():
694 """Simple function to error out if running with uid == 0"""
695 if os.getuid() == 0:
Simran Basi1bf60eb2015-12-01 16:39:29 -0800696 raise error.IllegalUser('This script can not be ran as root.')
697
698
699def get_hostname_from_machine(machine):
700 """Lookup hostname from a machine string or dict.
701
702 @returns: Machine hostname in string format.
703 """
704 hostname, _ = get_host_info_from_machine(machine)
705 return hostname
706
707
708def get_host_info_from_machine(machine):
709 """Lookup host information from a machine string or dict.
710
Kevin Cheng05ae2a42016-06-06 10:12:48 -0700711 @returns: Tuple of (hostname, afe_host)
Simran Basi1bf60eb2015-12-01 16:39:29 -0800712 """
713 if isinstance(machine, dict):
Kevin Cheng05ae2a42016-06-06 10:12:48 -0700714 return (machine['hostname'], machine['afe_host'])
Simran Basi1bf60eb2015-12-01 16:39:29 -0800715 else:
Kevin Cheng05ae2a42016-06-06 10:12:48 -0700716 return (machine, EmptyAFEHost())
717
718
719def get_afe_host_from_machine(machine):
720 """Return the afe_host from the machine dict if possible.
721
722 @returns: AFE host object.
723 """
724 _, afe_host = get_host_info_from_machine(machine)
725 return afe_host
Fang Dengf8a94e22015-12-07 13:39:13 -0800726
727
Hidehiko Abe06893302017-06-24 07:32:38 +0900728def get_connection_pool_from_machine(machine):
729 """Returns the ssh_multiplex.ConnectionPool from machine if possible."""
730 if not isinstance(machine, dict):
731 return None
732 return machine.get('connection_pool')
733
734
Fang Dengf8a94e22015-12-07 13:39:13 -0800735def get_creds_abspath(creds_file):
736 """Returns the abspath of the credentials file.
737
738 If creds_file is already an absolute path, just return it.
739 Otherwise, assume it is located in the creds directory
740 specified in global_config and return the absolute path.
741
742 @param: creds_path, a path to the credentials.
743 @return: An absolute path to the credentials file.
744 """
745 if not creds_file:
746 return None
747 if os.path.isabs(creds_file):
748 return creds_file
Dan Shid37736b2016-07-06 15:10:29 -0700749 creds_dir = CONFIG.get_config_value('SERVER', 'creds_dir', default='')
Fang Dengf8a94e22015-12-07 13:39:13 -0800750 if not creds_dir or not os.path.exists(creds_dir):
751 creds_dir = common.autotest_dir
752 return os.path.join(creds_dir, creds_file)
Kevin Cheng3b111812015-12-15 11:52:08 -0800753
754
755def machine_is_testbed(machine):
756 """Checks if the machine is a testbed.
757
758 The signal we use to determine if the machine is a testbed
759 is if the host attributes contain more than 1 serial.
760
761 @param machine: is a list of dicts
762
763 @return: True if the machine is a testbed, False otherwise.
764 """
Kevin Cheng05ae2a42016-06-06 10:12:48 -0700765 _, afe_host = get_host_info_from_machine(machine)
766 return len(afe_host.attributes.get('serials', '').split(',')) > 1
Paul Hobbs20cc72a2016-08-30 16:57:05 -0700767
768
769def SetupTsMonGlobalState(*args, **kwargs):
770 """Import-safe wrap around chromite.lib.ts_mon_config's setup function.
771
772 @param *args: Args to pass through.
773 @param **kwargs: Kwargs to pass through.
774 """
Prathmesh Prabhu16b46f82017-07-05 12:59:27 -0700775 try:
776 # TODO(crbug.com/739466) This module import is delayed because it adds
777 # 1-2 seconds to the module import time and most users of site_utils
778 # don't need it. The correct fix is to break apart site_utils into more
779 # meaningful chunks.
780 from chromite.lib import ts_mon_config
781 except ImportError:
782 logging.warn('Unable to import chromite. Monarch is disabled.')
Paul Hobbs604fc872016-09-29 16:41:55 -0700783 return TrivialContextManager()
Prathmesh Prabhu16b46f82017-07-05 12:59:27 -0700784
785 try:
786 context = ts_mon_config.SetupTsMonGlobalState(*args, **kwargs)
787 if hasattr(context, '__exit__'):
788 return context
789 except Exception as e:
790 logging.warning('Caught an exception trying to setup ts_mon, '
791 'monitoring is disabled: %s', e, exc_info=True)
792 return TrivialContextManager()
Paul Hobbs20cc72a2016-08-30 16:57:05 -0700793
794
795@contextlib.contextmanager
Paul Hobbs604fc872016-09-29 16:41:55 -0700796def TrivialContextManager(*args, **kwargs):
797 """Context manager that does nothing.
798
799 @param *args: Ignored args
800 @param **kwargs: Ignored kwargs.
801 """
Paul Hobbs20cc72a2016-08-30 16:57:05 -0700802 yield
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -0700803
804
805def wait_for_idle_duts(duts, afe, max_wait=IDLE_DUT_WAIT_TIMEOUT):
806 """Wait for the hosts to all go idle.
807
808 @param duts: List of duts to check for idle state.
809 @param afe: afe instance.
810 @param max_wait: Max wait time in seconds.
811
812 @returns Boolean True if all hosts are idle or False if any hosts did not
813 go idle within max_wait.
814 """
815 start_time = time.time()
816 # We make a shallow copy since we're going to be modifying active_dut_list.
817 active_dut_list = duts[:]
818 while active_dut_list:
819 # Let's rate-limit how often we hit the AFE.
820 time.sleep(1)
821
822 # Check if we've waited too long.
823 if (time.time() - start_time) > max_wait:
824 return False
825
826 idle_duts = []
827 # Get the status for the duts and see if they're in the idle state.
828 afe_hosts = afe.get_hosts(active_dut_list)
829 idle_duts = [afe_host.hostname for afe_host in afe_hosts
830 if afe_host.status in host_states.IDLE_STATES]
831
832 # Take out idle duts so we don't needlessly check them
833 # next time around.
834 for idle_dut in idle_duts:
835 active_dut_list.remove(idle_dut)
836
837 logging.info('still waiting for following duts to go idle: %s',
838 active_dut_list)
839 return True
840
841
842@contextlib.contextmanager
843def lock_duts_and_wait(duts, afe, lock_msg='default lock message',
844 max_wait=IDLE_DUT_WAIT_TIMEOUT):
845 """Context manager to lock the duts and wait for them to go idle.
846
847 @param duts: List of duts to lock.
848 @param afe: afe instance.
Hidehiko Abe06893302017-06-24 07:32:38 +0900849 @param lock_msg: message for afe on locking this host.
850 @param max_wait: Max wait time in seconds.
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -0700851
852 @returns Boolean lock_success where True if all duts locked successfully or
853 False if we timed out waiting too long for hosts to go idle.
854 """
855 try:
856 locked_duts = []
857 duts.sort()
858 for dut in duts:
859 if afe.lock_host(dut, lock_msg, fail_if_locked=True):
860 locked_duts.append(dut)
861 else:
862 logging.info('%s already locked', dut)
863 yield wait_for_idle_duts(locked_duts, afe, max_wait)
864 finally:
865 afe.unlock_hosts(locked_duts)
Dan Shib5b8b4f2016-11-02 14:04:02 -0700866
867
868def board_labels_allowed(boards):
869 """Check if the list of board labels can be set to a single host.
870
871 The only case multiple board labels can be set to a single host is for
872 testbed, which may have a list of board labels like
873 board:angler-1, board:angler-2, board:angler-3, board:marlin-1'
874
875 @param boards: A list of board labels (may include platform label).
876
877 @returns True if the the list of boards can be set to a single host.
878 """
879 # Filter out any non-board labels
880 boards = [b for b in boards if re.match('board:.*', b)]
881 if len(boards) <= 1:
882 return True
883 for board in boards:
884 if not re.match('board:[^-]+-\d+', board):
885 return False
886 return True