blob: 475db2cd2ec2939dcbfc1f99e7967aefc4dc93cc [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
Dan Shi82997b92015-05-06 12:08:02 -070027try:
28 from chromite.lib import cros_build_lib
Paul Hobbs20cc72a2016-08-30 16:57:05 -070029 from chromite.lib import ts_mon_config
Dan Shi82997b92015-05-06 12:08:02 -070030except ImportError:
Paul Hobbs20cc72a2016-08-30 16:57:05 -070031 logging.warn('Unable to import chromite. Monarch is disabled.')
Dan Shi82997b92015-05-06 12:08:02 -070032 # Init the module variable to None. Access to this module can check if it
33 # is not None before making calls.
34 cros_build_lib = None
Paul Hobbs20cc72a2016-08-30 16:57:05 -070035 ts_mon_config = None
Dan Shi82997b92015-05-06 12:08:02 -070036
Dan Shia1ecd5c2013-06-06 11:21:31 -070037
Dan Shid37736b2016-07-06 15:10:29 -070038CONFIG = global_config.global_config
39
40_SHERIFF_JS = CONFIG.get_config_value('NOTIFICATIONS', 'sheriffs', default='')
41_LAB_SHERIFF_JS = CONFIG.get_config_value(
42 'NOTIFICATIONS', 'lab_sheriffs', default='')
43_CHROMIUM_BUILD_URL = CONFIG.get_config_value(
44 'NOTIFICATIONS', 'chromium_build_url', default='')
Alex Millerdadc2c22013-07-08 15:21:21 -070045
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -080046LAB_GOOD_STATES = ('open', 'throttled')
47
Dan Shid37736b2016-07-06 15:10:29 -070048ENABLE_DRONE_IN_RESTRICTED_SUBNET = CONFIG.get_config_value(
49 'CROS', 'enable_drone_in_restricted_subnet', type=bool,
50 default=False)
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -080051
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -070052# Wait at most 10 mins for duts to go idle.
53IDLE_DUT_WAIT_TIMEOUT = 600
54
Dan Shi43274402016-11-04 15:13:43 -070055# Mapping between board name and build target. This is for special case handling
56# for certain Android board that the board name and build target name does not
57# match.
58ANDROID_TARGET_TO_BOARD_MAP = {'seed_l8150': 'gm4g_sprout'}
59
J. Richard Barnetteabbe0962013-12-10 18:15:44 -080060class TestLabException(Exception):
61 """Exception raised when the Test Lab blocks a test or suite."""
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -080062 pass
63
64
65class ParseBuildNameException(Exception):
66 """Raised when ParseBuildName() cannot parse a build name."""
67 pass
68
69
Fang Dengf08814a2015-08-03 18:12:18 +000070class Singleton(type):
71 """Enforce that only one client class is instantiated per process."""
72 _instances = {}
73
74 def __call__(cls, *args, **kwargs):
75 """Fetch the instance of a class to use for subsequent calls."""
76 if cls not in cls._instances:
77 cls._instances[cls] = super(Singleton, cls).__call__(
78 *args, **kwargs)
79 return cls._instances[cls]
80
Kevin Cheng05ae2a42016-06-06 10:12:48 -070081class EmptyAFEHost(object):
82 """Object to represent an AFE host object when there is no AFE."""
83
84 def __init__(self):
85 """
86 We'll be setting the instance attributes as we use them. Right now
87 we only use attributes and labels but as time goes by and other
88 attributes are used from an actual AFE Host object (check
89 rpc_interfaces.get_hosts()), we'll add them in here so users won't be
90 perplexed why their host's afe_host object complains that attribute
91 doesn't exist.
92 """
93 self.attributes = {}
94 self.labels = []
95
Fang Dengf08814a2015-08-03 18:12:18 +000096
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -080097def ParseBuildName(name):
98 """Format a build name, given board, type, milestone, and manifest num.
99
Simran Basib7d21162014-05-21 15:26:16 -0700100 @param name: a build name, e.g. 'x86-alex-release/R20-2015.0.0' or a
101 relative build name, e.g. 'x86-alex-release/LATEST'
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800102
103 @return board: board the manifest is for, e.g. x86-alex.
104 @return type: one of 'release', 'factory', or 'firmware'
105 @return milestone: (numeric) milestone the manifest was associated with.
Simran Basib7d21162014-05-21 15:26:16 -0700106 Will be None for relative build names.
107 @return manifest: manifest number, e.g. '2015.0.0'.
108 Will be None for relative build names.
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800109
110 """
Dan Shie02810d2016-08-25 09:44:57 -0700111 match = re.match(r'(trybot-)?(?P<board>[\w-]+?)(?:-chrome)?(?:-chromium)?'
112 r'-(?P<type>\w+)/(R(?P<milestone>\d+)-'
113 r'(?P<manifest>[\d.ab-]+)|LATEST)',
Simran Basif8f648e2014-09-09 11:40:03 -0700114 name)
115 if match and len(match.groups()) >= 5:
Simran Basib7d21162014-05-21 15:26:16 -0700116 return (match.group('board'), match.group('type'),
117 match.group('milestone'), match.group('manifest'))
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800118 raise ParseBuildNameException('%s is a malformed build name.' % name)
119
Alex Millerdadc2c22013-07-08 15:21:21 -0700120
Dan Shi3d7a0e12015-10-12 11:55:45 -0700121def get_labels_from_afe(hostname, label_prefix, afe):
122 """Retrieve a host's specific labels from the AFE.
123
124 Looks for the host labels that have the form <label_prefix>:<value>
125 and returns the "<value>" part of the label. None is returned
126 if there is not a label matching the pattern
127
128 @param hostname: hostname of given DUT.
129 @param label_prefix: prefix of label to be matched, e.g., |board:|
130 @param afe: afe instance.
131
132 @returns A list of labels that match the prefix or 'None'
133
134 """
135 labels = afe.get_labels(name__startswith=label_prefix,
136 host__hostname__in=[hostname])
137 if labels:
138 return [l.name.split(label_prefix, 1)[1] for l in labels]
139
140
Dan Shia1ecd5c2013-06-06 11:21:31 -0700141def get_label_from_afe(hostname, label_prefix, afe):
142 """Retrieve a host's specific label from the AFE.
143
144 Looks for a host label that has the form <label_prefix>:<value>
145 and returns the "<value>" part of the label. None is returned
146 if there is not a label matching the pattern
147
148 @param hostname: hostname of given DUT.
149 @param label_prefix: prefix of label to be matched, e.g., |board:|
150 @param afe: afe instance.
151 @returns the label that matches the prefix or 'None'
152
153 """
Dan Shi3d7a0e12015-10-12 11:55:45 -0700154 labels = get_labels_from_afe(hostname, label_prefix, afe)
Dan Shia1ecd5c2013-06-06 11:21:31 -0700155 if labels and len(labels) == 1:
Dan Shi3d7a0e12015-10-12 11:55:45 -0700156 return labels[0]
Dan Shia1ecd5c2013-06-06 11:21:31 -0700157
158
159def get_board_from_afe(hostname, afe):
160 """Retrieve given host's board from its labels in the AFE.
161
162 Looks for a host label of the form "board:<board>", and
163 returns the "<board>" part of the label. `None` is returned
164 if there is not a single, unique label matching the pattern.
165
166 @param hostname: hostname of given DUT.
167 @param afe: afe instance.
168 @returns board from label, or `None`.
169
170 """
171 return get_label_from_afe(hostname, constants.BOARD_PREFIX, afe)
172
173
174def get_build_from_afe(hostname, afe):
175 """Retrieve the current build for given host from the AFE.
176
177 Looks through the host's labels in the AFE to determine its build.
178
179 @param hostname: hostname of given DUT.
180 @param afe: afe instance.
181 @returns The current build or None if it could not find it or if there
182 were multiple build labels assigned to this host.
183
184 """
Simran Basi7756a0b2016-03-16 13:10:07 -0700185 for prefix in [provision.CROS_VERSION_PREFIX,
186 provision.ANDROID_BUILD_VERSION_PREFIX]:
187 build = get_label_from_afe(hostname, prefix + ':', afe)
188 if build:
189 return build
190 return None
Dan Shia1ecd5c2013-06-06 11:21:31 -0700191
192
Allen Li6a612392016-08-18 12:09:32 -0700193# TODO(fdeng): fix get_sheriffs crbug.com/483254
Fang Deng3197b392013-06-26 11:42:02 -0700194def get_sheriffs(lab_only=False):
Alex Millerdadc2c22013-07-08 15:21:21 -0700195 """
196 Polls the javascript file that holds the identity of the sheriff and
197 parses it's output to return a list of chromium sheriff email addresses.
198 The javascript file can contain the ldap of more than one sheriff, eg:
199 document.write('sheriff_one, sheriff_two').
200
Fang Deng3197b392013-06-26 11:42:02 -0700201 @param lab_only: if True, only pulls lab sheriff.
202 @return: A list of chroium.org sheriff email addresses to cc on the bug.
203 An empty list if failed to parse the javascript.
Alex Millerdadc2c22013-07-08 15:21:21 -0700204 """
205 sheriff_ids = []
Fang Deng3197b392013-06-26 11:42:02 -0700206 sheriff_js_list = _LAB_SHERIFF_JS.split(',')
207 if not lab_only:
208 sheriff_js_list.extend(_SHERIFF_JS.split(','))
209
210 for sheriff_js in sheriff_js_list:
Alex Millerdadc2c22013-07-08 15:21:21 -0700211 try:
Dan Shief31f032016-05-13 15:51:39 -0700212 url_content = utils.urlopen('%s%s'% (
Alex Millerdadc2c22013-07-08 15:21:21 -0700213 _CHROMIUM_BUILD_URL, sheriff_js)).read()
214 except (ValueError, IOError) as e:
beeps4efdf032013-09-17 11:27:14 -0700215 logging.warning('could not parse sheriff from url %s%s: %s',
216 _CHROMIUM_BUILD_URL, sheriff_js, str(e))
Paul Drewsbef578d2013-09-24 15:10:36 -0700217 except (urllib2.URLError, httplib.HTTPException) as e:
218 logging.warning('unexpected error reading from url "%s%s": %s',
219 _CHROMIUM_BUILD_URL, sheriff_js, str(e))
Alex Millerdadc2c22013-07-08 15:21:21 -0700220 else:
221 ldaps = re.search(r"document.write\('(.*)'\)", url_content)
222 if not ldaps:
beeps4efdf032013-09-17 11:27:14 -0700223 logging.warning('Could not retrieve sheriff ldaps for: %s',
224 url_content)
Alex Millerdadc2c22013-07-08 15:21:21 -0700225 continue
226 sheriff_ids += ['%s@chromium.org' % alias.replace(' ', '')
227 for alias in ldaps.group(1).split(',')]
228 return sheriff_ids
beeps46dadc92013-11-07 14:07:10 -0800229
230
231def remote_wget(source_url, dest_path, ssh_cmd):
232 """wget source_url from localhost to dest_path on remote host using ssh.
233
234 @param source_url: The complete url of the source of the package to send.
235 @param dest_path: The path on the remote host's file system where we would
236 like to store the package.
237 @param ssh_cmd: The ssh command to use in performing the remote wget.
238 """
239 wget_cmd = ("wget -O - %s | %s 'cat >%s'" %
240 (source_url, ssh_cmd, dest_path))
Dan Shief31f032016-05-13 15:51:39 -0700241 utils.run(wget_cmd)
beeps46dadc92013-11-07 14:07:10 -0800242
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800243
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800244_MAX_LAB_STATUS_ATTEMPTS = 5
245def _get_lab_status(status_url):
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800246 """Grabs the current lab status and message.
247
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800248 @returns The JSON object obtained from the given URL.
249
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800250 """
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800251 retry_waittime = 1
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800252 for _ in range(_MAX_LAB_STATUS_ATTEMPTS):
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800253 try:
254 response = urllib2.urlopen(status_url)
255 except IOError as e:
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800256 logging.debug('Error occurred when grabbing the lab status: %s.',
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800257 e)
258 time.sleep(retry_waittime)
259 continue
260 # Check for successful response code.
261 if response.getcode() == 200:
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800262 return json.load(response)
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800263 time.sleep(retry_waittime)
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800264 return None
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800265
266
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800267def _decode_lab_status(lab_status, build):
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800268 """Decode lab status, and report exceptions as needed.
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800269
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800270 Take a deserialized JSON object from the lab status page, and
271 interpret it to determine the actual lab status. Raise
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800272 exceptions as required to report when the lab is down.
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800273
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800274 @param build: build name that we want to check the status of.
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800275
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800276 @raises TestLabException Raised if a request to test for the given
277 status and build should be blocked.
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800278 """
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800279 # First check if the lab is up.
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800280 if not lab_status['general_state'] in LAB_GOOD_STATES:
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800281 raise TestLabException('Chromium OS Test Lab is closed: '
282 '%s.' % lab_status['message'])
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800283
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800284 # Check if the build we wish to use is disabled.
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800285 # Lab messages should be in the format of:
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800286 # Lab is 'status' [regex ...] (comment)
287 # If the build name matches any regex, it will be blocked.
288 build_exceptions = re.search('\[(.*)\]', lab_status['message'])
Prashanth Balasubramanianae437212014-10-27 11:17:26 -0700289 if not build_exceptions or not build:
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800290 return
291 for build_pattern in build_exceptions.group(1).split():
J. Richard Barnette7f215d32015-06-19 12:44:38 -0700292 if re.match(build_pattern, build):
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800293 raise TestLabException('Chromium OS Test Lab is closed: '
294 '%s matches %s.' % (
295 build, build_pattern))
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800296 return
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800297
298
Dan Shi94234cb2014-05-23 20:04:31 -0700299def is_in_lab():
300 """Check if current Autotest instance is in lab
301
302 @return: True if the Autotest instance is in lab.
303 """
Dan Shid37736b2016-07-06 15:10:29 -0700304 test_server_name = CONFIG.get_config_value('SERVER', 'hostname')
Dan Shi94234cb2014-05-23 20:04:31 -0700305 return test_server_name.startswith('cautotest')
306
307
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800308def check_lab_status(build):
309 """Check if the lab status allows us to schedule for a build.
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800310
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800311 Checks if the lab is down, or if testing for the requested build
312 should be blocked.
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800313
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800314 @param build: Name of the build to be scheduled for testing.
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800315
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800316 @raises TestLabException Raised if a request to test for the given
317 status and build should be blocked.
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800318
319 """
320 # Ensure we are trying to schedule on the actual lab.
Dan Shi94234cb2014-05-23 20:04:31 -0700321 if not is_in_lab():
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800322 return
323
324 # Download the lab status from its home on the web.
Dan Shid37736b2016-07-06 15:10:29 -0700325 status_url = CONFIG.get_config_value('CROS', 'lab_status_url')
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800326 json_status = _get_lab_status(status_url)
327 if json_status is None:
328 # We go ahead and say the lab is open if we can't get the status.
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700329 logging.warning('Could not get a status from %s', status_url)
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800330 return
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800331 _decode_lab_status(json_status, build)
beeps023afc62014-02-04 16:59:22 -0800332
333
334def lock_host_with_labels(afe, lock_manager, labels):
335 """Lookup and lock one host that matches the list of input labels.
336
337 @param afe: An instance of the afe class, as defined in server.frontend.
338 @param lock_manager: A lock manager capable of locking hosts, eg the
339 one defined in server.cros.host_lock_manager.
340 @param labels: A list of labels to look for on hosts.
341
342 @return: The hostname of a host matching all labels, and locked through the
343 lock_manager. The hostname will be as specified in the database the afe
344 object is associated with, i.e if it exists in afe_hosts with a .cros
345 suffix, the hostname returned will contain a .cros suffix.
346
347 @raises: error.NoEligibleHostException: If no hosts matching the list of
348 input labels are available.
349 @raises: error.TestError: If unable to lock a host matching the labels.
350 """
351 potential_hosts = afe.get_hosts(multiple_labels=labels)
352 if not potential_hosts:
353 raise error.NoEligibleHostException(
354 'No devices found with labels %s.' % labels)
355
356 # This prevents errors where a fault might seem repeatable
357 # because we lock, say, the same packet capturer for each test run.
358 random.shuffle(potential_hosts)
359 for host in potential_hosts:
360 if lock_manager.lock([host.hostname]):
361 logging.info('Locked device %s with labels %s.',
362 host.hostname, labels)
363 return host.hostname
364 else:
365 logging.info('Unable to lock device %s with labels %s.',
366 host.hostname, labels)
367
368 raise error.TestError('Could not lock a device with labels %s' % labels)
Dan Shi7e04fa82013-07-25 15:08:48 -0700369
370
371def get_test_views_from_tko(suite_job_id, tko):
372 """Get test name and result for given suite job ID.
373
374 @param suite_job_id: ID of suite job.
375 @param tko: an instance of TKO as defined in server/frontend.py.
376 @return: A dictionary of test status keyed by test name, e.g.,
377 {'dummy_Fail.Error': 'ERROR', 'dummy_Fail.NAError': 'TEST_NA'}
378 @raise: Exception when there is no test view found.
379
380 """
381 views = tko.run('get_detailed_test_views', afe_job_id=suite_job_id)
382 relevant_views = filter(job_status.view_is_relevant, views)
383 if not relevant_views:
384 raise Exception('Failed to retrieve job results.')
385
386 test_views = {}
387 for view in relevant_views:
388 test_views[view['test_name']] = view['status']
389
390 return test_views
MK Ryu35d661e2014-09-25 17:44:10 -0700391
392
MK Ryuc9c0c3f2014-10-27 14:36:01 -0700393def get_data_key(prefix, suite, build, board):
394 """
395 Constructs a key string from parameters.
396
397 @param prefix: Prefix for the generating key.
398 @param suite: a suite name. e.g., bvt-cq, bvt-inline, dummy
399 @param build: The build string. This string should have a consistent
400 format eg: x86-mario-release/R26-3570.0.0. If the format of this
401 string changes such that we can't determine build_type or branch
402 we give up and use the parametes we're sure of instead (suite,
403 board). eg:
404 1. build = x86-alex-pgo-release/R26-3570.0.0
405 branch = 26
406 build_type = pgo-release
407 2. build = lumpy-paladin/R28-3993.0.0-rc5
408 branch = 28
409 build_type = paladin
410 @param board: The board that this suite ran on.
411 @return: The key string used for a dictionary.
412 """
413 try:
414 _board, build_type, branch = ParseBuildName(build)[:3]
415 except ParseBuildNameException as e:
416 logging.error(str(e))
417 branch = 'Unknown'
418 build_type = 'Unknown'
419 else:
420 embedded_str = re.search(r'x86-\w+-(.*)', _board)
421 if embedded_str:
422 build_type = embedded_str.group(1) + '-' + build_type
423
424 data_key_dict = {
425 'prefix': prefix,
426 'board': board,
427 'branch': branch,
428 'build_type': build_type,
429 'suite': suite,
430 }
431 return ('%(prefix)s.%(board)s.%(build_type)s.%(branch)s.%(suite)s'
432 % data_key_dict)
MK Ryu83184352014-12-10 14:59:40 -0800433
434
MK Ryu2d0a3642015-01-07 15:11:19 -0800435def setup_logging(logfile=None, prefix=False):
MK Ryu83184352014-12-10 14:59:40 -0800436 """Setup basic logging with all logging info stripped.
437
438 Calls to logging will only show the message. No severity is logged.
439
440 @param logfile: If specified dump output to a file as well.
MK Ryu2d0a3642015-01-07 15:11:19 -0800441 @param prefix: Flag for log prefix. Set to True to add prefix to log
442 entries to include timestamp and log level. Default is False.
MK Ryu83184352014-12-10 14:59:40 -0800443 """
444 # Remove all existing handlers. client/common_lib/logging_config adds
445 # a StreamHandler to logger when modules are imported, e.g.,
446 # autotest_lib.client.bin.utils. A new StreamHandler will be added here to
447 # log only messages, not severity.
448 logging.getLogger().handlers = []
449
MK Ryu2d0a3642015-01-07 15:11:19 -0800450 if prefix:
451 log_format = '%(asctime)s %(levelname)-5s| %(message)s'
452 else:
453 log_format = '%(message)s'
454
MK Ryu83184352014-12-10 14:59:40 -0800455 screen_handler = logging.StreamHandler()
MK Ryu2d0a3642015-01-07 15:11:19 -0800456 screen_handler.setFormatter(logging.Formatter(log_format))
MK Ryu83184352014-12-10 14:59:40 -0800457 logging.getLogger().addHandler(screen_handler)
458 logging.getLogger().setLevel(logging.INFO)
459 if logfile:
460 file_handler = logging.FileHandler(logfile)
MK Ryu2d0a3642015-01-07 15:11:19 -0800461 file_handler.setFormatter(logging.Formatter(log_format))
MK Ryu83184352014-12-10 14:59:40 -0800462 file_handler.setLevel(logging.DEBUG)
463 logging.getLogger().addHandler(file_handler)
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -0800464
465
466def is_shard():
467 """Determines if this instance is running as a shard.
468
469 Reads the global_config value shard_hostname in the section SHARD.
470
471 @return True, if shard_hostname is set, False otherwise.
472 """
Dan Shid37736b2016-07-06 15:10:29 -0700473 hostname = CONFIG.get_config_value('SHARD', 'shard_hostname', default=None)
MK Ryu0c1a37d2015-04-30 12:00:55 -0700474 return bool(hostname)
475
476
Fang Deng0cb2a3b2015-12-10 17:59:00 -0800477def get_global_afe_hostname():
478 """Read the hostname of the global AFE from the global configuration."""
Dan Shid37736b2016-07-06 15:10:29 -0700479 return CONFIG.get_config_value('SERVER', 'global_afe_hostname')
Fang Deng0cb2a3b2015-12-10 17:59:00 -0800480
481
Fang Deng18699fe2015-12-04 16:40:27 -0800482def is_restricted_user(username):
483 """Determines if a user is in a restricted group.
484
485 User in restricted group only have access to master.
486
487 @param username: A string, representing a username.
488
489 @returns: True if the user is in a restricted group.
490 """
491 if not username:
492 return False
493
Dan Shid37736b2016-07-06 15:10:29 -0700494 restricted_groups = CONFIG.get_config_value(
Fang Deng18699fe2015-12-04 16:40:27 -0800495 'AUTOTEST_WEB', 'restricted_groups', default='').split(',')
496 for group in restricted_groups:
Fang Deng5229c852016-02-09 13:30:31 -0800497 try:
498 if group and username in grp.getgrnam(group).gr_mem:
499 return True
500 except KeyError as e:
501 logging.debug("%s is not a valid group.", group)
Fang Deng18699fe2015-12-04 16:40:27 -0800502 return False
503
504
MK Ryu0c1a37d2015-04-30 12:00:55 -0700505def get_special_task_status(is_complete, success, is_active):
506 """Get the status of a special task.
507
508 Emulate a host queue entry status for a special task
509 Although SpecialTasks are not HostQueueEntries, it is helpful to
510 the user to present similar statuses.
511
512 @param is_complete Boolean if the task is completed.
513 @param success Boolean if the task succeeded.
514 @param is_active Boolean if the task is active.
515
516 @return The status of a special task.
517 """
518 if is_complete:
519 if success:
520 return host_queue_entry_states.Status.COMPLETED
521 return host_queue_entry_states.Status.FAILED
522 if is_active:
523 return host_queue_entry_states.Status.RUNNING
524 return host_queue_entry_states.Status.QUEUED
525
526
527def get_special_task_exec_path(hostname, task_id, task_name, time_requested):
528 """Get the execution path of the SpecialTask.
529
530 This method returns different paths depending on where a
531 the task ran:
532 * Master: hosts/hostname/task_id-task_type
533 * Shard: Master_path/time_created
534 This is to work around the fact that a shard can fail independent
535 of the master, and be replaced by another shard that has the same
536 hosts. Without the time_created stamp the logs of the tasks running
537 on the second shard will clobber the logs from the first in google
538 storage, because task ids are not globally unique.
539
540 @param hostname Hostname
541 @param task_id Special task id
542 @param task_name Special task name (e.g., Verify, Repair, etc)
543 @param time_requested Special task requested time.
544
545 @return An execution path for the task.
546 """
547 results_path = 'hosts/%s/%s-%s' % (hostname, task_id, task_name.lower())
548
549 # If we do this on the master it will break backward compatibility,
550 # as there are tasks that currently don't have timestamps. If a host
551 # or job has been sent to a shard, the rpc for that host/job will
552 # be redirected to the shard, so this global_config check will happen
553 # on the shard the logs are on.
554 if not is_shard():
555 return results_path
556
557 # Generate a uid to disambiguate special task result directories
558 # in case this shard fails. The simplest uid is the job_id, however
559 # in rare cases tasks do not have jobs associated with them (eg:
560 # frontend verify), so just use the creation timestamp. The clocks
561 # between a shard and master should always be in sync. Any discrepancies
562 # will be brought to our attention in the form of job timeouts.
563 uid = time_requested.strftime('%Y%d%m%H%M%S')
564
565 # TODO: This is a hack, however it is the easiest way to achieve
566 # correctness. There is currently some debate over the future of
567 # tasks in our infrastructure and refactoring everything right
568 # now isn't worth the time.
569 return '%s/%s' % (results_path, uid)
570
571
572def get_job_tag(id, owner):
573 """Returns a string tag for a job.
574
575 @param id Job id
576 @param owner Job owner
577
578 """
579 return '%s-%s' % (id, owner)
580
581
582def get_hqe_exec_path(tag, execution_subdir):
583 """Returns a execution path to a HQE's results.
584
585 @param tag Tag string for a job associated with a HQE.
586 @param execution_subdir Execution sub-directory string of a HQE.
587
588 """
589 return os.path.join(tag, execution_subdir)
Dan Shi82997b92015-05-06 12:08:02 -0700590
591
592def is_inside_chroot():
593 """Check if the process is running inside chroot.
594
595 This is a wrapper around chromite.lib.cros_build_lib.IsInsideChroot(). The
596 method checks if cros_build_lib can be imported first.
597
598 @return: True if the process is running inside chroot or cros_build_lib
599 cannot be imported.
600
601 """
J. Richard Barnette7f215d32015-06-19 12:44:38 -0700602 return not cros_build_lib or cros_build_lib.IsInsideChroot()
Dan Shi70647ca2015-07-16 22:52:35 -0700603
604
605def parse_job_name(name):
606 """Parse job name to get information including build, board and suite etc.
607
608 Suite job created by run_suite follows the naming convention of:
609 [build]-test_suites/control.[suite]
610 For example: lumpy-release/R46-7272.0.0-test_suites/control.bvt
611 The naming convention is defined in site_rpc_interface.create_suite_job.
612
613 Test job created by suite job follows the naming convention of:
614 [build]/[suite]/[test name]
615 For example: lumpy-release/R46-7272.0.0/bvt/login_LoginSuccess
616 The naming convention is defined in
617 server/cros/dynamic_suite/tools.create_job_name
618
619 Note that pgo and chrome-perf builds will fail the method. Since lab does
620 not run test for these builds, they can be ignored.
Dan Shief31f032016-05-13 15:51:39 -0700621 Also, tests for Launch Control builds have different naming convention.
622 The build ID will be used as build_version.
Dan Shi70647ca2015-07-16 22:52:35 -0700623
624 @param name: Name of the job.
625
626 @return: A dictionary containing the test information. The keyvals include:
627 build: Name of the build, e.g., lumpy-release/R46-7272.0.0
628 build_version: The version of the build, e.g., R46-7272.0.0
629 board: Name of the board, e.g., lumpy
630 suite: Name of the test suite, e.g., bvt
631
632 """
633 info = {}
Dan Shief31f032016-05-13 15:51:39 -0700634 suite_job_regex = '([^/]*/[^/]*(?:/\d+)?)-test_suites/control\.(.*)'
635 test_job_regex = '([^/]*/[^/]*(?:/\d+)?)/([^/]+)/.*'
Dan Shi70647ca2015-07-16 22:52:35 -0700636 match = re.match(suite_job_regex, name)
637 if not match:
638 match = re.match(test_job_regex, name)
639 if match:
640 info['build'] = match.groups()[0]
641 info['suite'] = match.groups()[1]
642 info['build_version'] = info['build'].split('/')[1]
643 try:
644 info['board'], _, _, _ = ParseBuildName(info['build'])
645 except ParseBuildNameException:
Dan Shief31f032016-05-13 15:51:39 -0700646 # Try to parse it as Launch Control build
647 # Launch Control builds have name format:
648 # branch/build_target-build_type/build_id.
649 try:
650 _, target, build_id = utils.parse_launch_control_build(
651 info['build'])
652 build_target, _ = utils.parse_launch_control_target(target)
653 if build_target:
654 info['board'] = build_target
655 info['build_version'] = build_id
656 except ValueError:
657 pass
Dan Shi70647ca2015-07-16 22:52:35 -0700658 return info
Kevin Cheng3a4a57a2015-09-30 12:09:50 -0700659
660
661def add_label_detector(label_function_list, label_list=None, label=None):
662 """Decorator used to group functions together into the provided list.
663
664 This is a helper function to automatically add label functions that have
665 the label decorator. This is to help populate the class list of label
666 functions to be retrieved by the get_labels class method.
667
668 @param label_function_list: List of label detecting functions to add
669 decorated function to.
670 @param label_list: List of detectable labels to add detectable labels to.
671 (Default: None)
672 @param label: Label string that is detectable by this detection function
673 (Default: None)
674 """
675 def add_func(func):
676 """
677 @param func: The function to be added as a detector.
678 """
679 label_function_list.append(func)
680 if label and label_list is not None:
681 label_list.append(label)
682 return func
683 return add_func
Simran Basi9f364a62015-12-07 14:15:19 -0800684
685
686def verify_not_root_user():
687 """Simple function to error out if running with uid == 0"""
688 if os.getuid() == 0:
Simran Basi1bf60eb2015-12-01 16:39:29 -0800689 raise error.IllegalUser('This script can not be ran as root.')
690
691
692def get_hostname_from_machine(machine):
693 """Lookup hostname from a machine string or dict.
694
695 @returns: Machine hostname in string format.
696 """
697 hostname, _ = get_host_info_from_machine(machine)
698 return hostname
699
700
701def get_host_info_from_machine(machine):
702 """Lookup host information from a machine string or dict.
703
Kevin Cheng05ae2a42016-06-06 10:12:48 -0700704 @returns: Tuple of (hostname, afe_host)
Simran Basi1bf60eb2015-12-01 16:39:29 -0800705 """
706 if isinstance(machine, dict):
Kevin Cheng05ae2a42016-06-06 10:12:48 -0700707 return (machine['hostname'], machine['afe_host'])
Simran Basi1bf60eb2015-12-01 16:39:29 -0800708 else:
Kevin Cheng05ae2a42016-06-06 10:12:48 -0700709 return (machine, EmptyAFEHost())
710
711
712def get_afe_host_from_machine(machine):
713 """Return the afe_host from the machine dict if possible.
714
715 @returns: AFE host object.
716 """
717 _, afe_host = get_host_info_from_machine(machine)
718 return afe_host
Fang Dengf8a94e22015-12-07 13:39:13 -0800719
720
721def get_creds_abspath(creds_file):
722 """Returns the abspath of the credentials file.
723
724 If creds_file is already an absolute path, just return it.
725 Otherwise, assume it is located in the creds directory
726 specified in global_config and return the absolute path.
727
728 @param: creds_path, a path to the credentials.
729 @return: An absolute path to the credentials file.
730 """
731 if not creds_file:
732 return None
733 if os.path.isabs(creds_file):
734 return creds_file
Dan Shid37736b2016-07-06 15:10:29 -0700735 creds_dir = CONFIG.get_config_value('SERVER', 'creds_dir', default='')
Fang Dengf8a94e22015-12-07 13:39:13 -0800736 if not creds_dir or not os.path.exists(creds_dir):
737 creds_dir = common.autotest_dir
738 return os.path.join(creds_dir, creds_file)
Kevin Cheng3b111812015-12-15 11:52:08 -0800739
740
741def machine_is_testbed(machine):
742 """Checks if the machine is a testbed.
743
744 The signal we use to determine if the machine is a testbed
745 is if the host attributes contain more than 1 serial.
746
747 @param machine: is a list of dicts
748
749 @return: True if the machine is a testbed, False otherwise.
750 """
Kevin Cheng05ae2a42016-06-06 10:12:48 -0700751 _, afe_host = get_host_info_from_machine(machine)
752 return len(afe_host.attributes.get('serials', '').split(',')) > 1
Paul Hobbs20cc72a2016-08-30 16:57:05 -0700753
754
755def SetupTsMonGlobalState(*args, **kwargs):
756 """Import-safe wrap around chromite.lib.ts_mon_config's setup function.
757
758 @param *args: Args to pass through.
759 @param **kwargs: Kwargs to pass through.
760 """
761 if ts_mon_config:
Paul Hobbs604fc872016-09-29 16:41:55 -0700762 try:
763 context = ts_mon_config.SetupTsMonGlobalState(*args, **kwargs)
764 if hasattr(context, '__exit__'):
765 return context
766 except Exception as e:
767 logging.warning('Caught an exception trying to setup ts_mon, '
768 'monitoring is disabled: %s', e, exc_info=True)
769 return TrivialContextManager()
Paul Hobbs20cc72a2016-08-30 16:57:05 -0700770 else:
Paul Hobbs930ed9d2016-09-28 15:59:08 -0700771 return TrivialContextManager()
Paul Hobbs20cc72a2016-08-30 16:57:05 -0700772
773
774@contextlib.contextmanager
Paul Hobbs604fc872016-09-29 16:41:55 -0700775def TrivialContextManager(*args, **kwargs):
776 """Context manager that does nothing.
777
778 @param *args: Ignored args
779 @param **kwargs: Ignored kwargs.
780 """
Paul Hobbs20cc72a2016-08-30 16:57:05 -0700781 yield
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -0700782
783
784def wait_for_idle_duts(duts, afe, max_wait=IDLE_DUT_WAIT_TIMEOUT):
785 """Wait for the hosts to all go idle.
786
787 @param duts: List of duts to check for idle state.
788 @param afe: afe instance.
789 @param max_wait: Max wait time in seconds.
790
791 @returns Boolean True if all hosts are idle or False if any hosts did not
792 go idle within max_wait.
793 """
794 start_time = time.time()
795 # We make a shallow copy since we're going to be modifying active_dut_list.
796 active_dut_list = duts[:]
797 while active_dut_list:
798 # Let's rate-limit how often we hit the AFE.
799 time.sleep(1)
800
801 # Check if we've waited too long.
802 if (time.time() - start_time) > max_wait:
803 return False
804
805 idle_duts = []
806 # Get the status for the duts and see if they're in the idle state.
807 afe_hosts = afe.get_hosts(active_dut_list)
808 idle_duts = [afe_host.hostname for afe_host in afe_hosts
809 if afe_host.status in host_states.IDLE_STATES]
810
811 # Take out idle duts so we don't needlessly check them
812 # next time around.
813 for idle_dut in idle_duts:
814 active_dut_list.remove(idle_dut)
815
816 logging.info('still waiting for following duts to go idle: %s',
817 active_dut_list)
818 return True
819
820
821@contextlib.contextmanager
822def lock_duts_and_wait(duts, afe, lock_msg='default lock message',
823 max_wait=IDLE_DUT_WAIT_TIMEOUT):
824 """Context manager to lock the duts and wait for them to go idle.
825
826 @param duts: List of duts to lock.
827 @param afe: afe instance.
828
829 @returns Boolean lock_success where True if all duts locked successfully or
830 False if we timed out waiting too long for hosts to go idle.
831 """
832 try:
833 locked_duts = []
834 duts.sort()
835 for dut in duts:
836 if afe.lock_host(dut, lock_msg, fail_if_locked=True):
837 locked_duts.append(dut)
838 else:
839 logging.info('%s already locked', dut)
840 yield wait_for_idle_duts(locked_duts, afe, max_wait)
841 finally:
842 afe.unlock_hosts(locked_duts)
Dan Shib5b8b4f2016-11-02 14:04:02 -0700843
844
845def board_labels_allowed(boards):
846 """Check if the list of board labels can be set to a single host.
847
848 The only case multiple board labels can be set to a single host is for
849 testbed, which may have a list of board labels like
850 board:angler-1, board:angler-2, board:angler-3, board:marlin-1'
851
852 @param boards: A list of board labels (may include platform label).
853
854 @returns True if the the list of boards can be set to a single host.
855 """
856 # Filter out any non-board labels
857 boards = [b for b in boards if re.match('board:.*', b)]
858 if len(boards) <= 1:
859 return True
860 for board in boards:
861 if not re.match('board:[^-]+-\d+', board):
862 return False
863 return True