blob: 68040bc8a0ae1f6ebd983944335fee13e6246cce [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
Simran Basi7756a0b2016-03-16 13:10:07 -070022from autotest_lib.server.cros import provision
Dan Shia1ecd5c2013-06-06 11:21:31 -070023from autotest_lib.server.cros.dynamic_suite import constants
Dan Shi7e04fa82013-07-25 15:08:48 -070024from autotest_lib.server.cros.dynamic_suite import job_status
Dan Shia1ecd5c2013-06-06 11:21:31 -070025
Dan Shi82997b92015-05-06 12:08:02 -070026try:
27 from chromite.lib import cros_build_lib
Paul Hobbs20cc72a2016-08-30 16:57:05 -070028 from chromite.lib import ts_mon_config
Dan Shi82997b92015-05-06 12:08:02 -070029except ImportError:
Paul Hobbs20cc72a2016-08-30 16:57:05 -070030 logging.warn('Unable to import chromite. Monarch is disabled.')
Dan Shi82997b92015-05-06 12:08:02 -070031 # Init the module variable to None. Access to this module can check if it
32 # is not None before making calls.
33 cros_build_lib = None
Paul Hobbs20cc72a2016-08-30 16:57:05 -070034 ts_mon_config = None
Dan Shi82997b92015-05-06 12:08:02 -070035
Dan Shia1ecd5c2013-06-06 11:21:31 -070036
Dan Shid37736b2016-07-06 15:10:29 -070037CONFIG = global_config.global_config
38
39_SHERIFF_JS = CONFIG.get_config_value('NOTIFICATIONS', 'sheriffs', default='')
40_LAB_SHERIFF_JS = CONFIG.get_config_value(
41 'NOTIFICATIONS', 'lab_sheriffs', default='')
42_CHROMIUM_BUILD_URL = CONFIG.get_config_value(
43 'NOTIFICATIONS', 'chromium_build_url', default='')
Alex Millerdadc2c22013-07-08 15:21:21 -070044
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -080045LAB_GOOD_STATES = ('open', 'throttled')
46
Dan Shid37736b2016-07-06 15:10:29 -070047ENABLE_DRONE_IN_RESTRICTED_SUBNET = CONFIG.get_config_value(
48 'CROS', 'enable_drone_in_restricted_subnet', type=bool,
49 default=False)
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -080050
J. Richard Barnetteabbe0962013-12-10 18:15:44 -080051class TestLabException(Exception):
52 """Exception raised when the Test Lab blocks a test or suite."""
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -080053 pass
54
55
56class ParseBuildNameException(Exception):
57 """Raised when ParseBuildName() cannot parse a build name."""
58 pass
59
60
Fang Dengf08814a2015-08-03 18:12:18 +000061class Singleton(type):
62 """Enforce that only one client class is instantiated per process."""
63 _instances = {}
64
65 def __call__(cls, *args, **kwargs):
66 """Fetch the instance of a class to use for subsequent calls."""
67 if cls not in cls._instances:
68 cls._instances[cls] = super(Singleton, cls).__call__(
69 *args, **kwargs)
70 return cls._instances[cls]
71
Kevin Cheng05ae2a42016-06-06 10:12:48 -070072class EmptyAFEHost(object):
73 """Object to represent an AFE host object when there is no AFE."""
74
75 def __init__(self):
76 """
77 We'll be setting the instance attributes as we use them. Right now
78 we only use attributes and labels but as time goes by and other
79 attributes are used from an actual AFE Host object (check
80 rpc_interfaces.get_hosts()), we'll add them in here so users won't be
81 perplexed why their host's afe_host object complains that attribute
82 doesn't exist.
83 """
84 self.attributes = {}
85 self.labels = []
86
Fang Dengf08814a2015-08-03 18:12:18 +000087
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -080088def ParseBuildName(name):
89 """Format a build name, given board, type, milestone, and manifest num.
90
Simran Basib7d21162014-05-21 15:26:16 -070091 @param name: a build name, e.g. 'x86-alex-release/R20-2015.0.0' or a
92 relative build name, e.g. 'x86-alex-release/LATEST'
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -080093
94 @return board: board the manifest is for, e.g. x86-alex.
95 @return type: one of 'release', 'factory', or 'firmware'
96 @return milestone: (numeric) milestone the manifest was associated with.
Simran Basib7d21162014-05-21 15:26:16 -070097 Will be None for relative build names.
98 @return manifest: manifest number, e.g. '2015.0.0'.
99 Will be None for relative build names.
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800100
101 """
Dan Shie02810d2016-08-25 09:44:57 -0700102 match = re.match(r'(trybot-)?(?P<board>[\w-]+?)(?:-chrome)?(?:-chromium)?'
103 r'-(?P<type>\w+)/(R(?P<milestone>\d+)-'
104 r'(?P<manifest>[\d.ab-]+)|LATEST)',
Simran Basif8f648e2014-09-09 11:40:03 -0700105 name)
106 if match and len(match.groups()) >= 5:
Simran Basib7d21162014-05-21 15:26:16 -0700107 return (match.group('board'), match.group('type'),
108 match.group('milestone'), match.group('manifest'))
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800109 raise ParseBuildNameException('%s is a malformed build name.' % name)
110
Alex Millerdadc2c22013-07-08 15:21:21 -0700111
Dan Shi3d7a0e12015-10-12 11:55:45 -0700112def get_labels_from_afe(hostname, label_prefix, afe):
113 """Retrieve a host's specific labels from the AFE.
114
115 Looks for the host labels that have the form <label_prefix>:<value>
116 and returns the "<value>" part of the label. None is returned
117 if there is not a label matching the pattern
118
119 @param hostname: hostname of given DUT.
120 @param label_prefix: prefix of label to be matched, e.g., |board:|
121 @param afe: afe instance.
122
123 @returns A list of labels that match the prefix or 'None'
124
125 """
126 labels = afe.get_labels(name__startswith=label_prefix,
127 host__hostname__in=[hostname])
128 if labels:
129 return [l.name.split(label_prefix, 1)[1] for l in labels]
130
131
Dan Shia1ecd5c2013-06-06 11:21:31 -0700132def get_label_from_afe(hostname, label_prefix, afe):
133 """Retrieve a host's specific label from the AFE.
134
135 Looks for a host label that has the form <label_prefix>:<value>
136 and returns the "<value>" part of the label. None is returned
137 if there is not a label matching the pattern
138
139 @param hostname: hostname of given DUT.
140 @param label_prefix: prefix of label to be matched, e.g., |board:|
141 @param afe: afe instance.
142 @returns the label that matches the prefix or 'None'
143
144 """
Dan Shi3d7a0e12015-10-12 11:55:45 -0700145 labels = get_labels_from_afe(hostname, label_prefix, afe)
Dan Shia1ecd5c2013-06-06 11:21:31 -0700146 if labels and len(labels) == 1:
Dan Shi3d7a0e12015-10-12 11:55:45 -0700147 return labels[0]
Dan Shia1ecd5c2013-06-06 11:21:31 -0700148
149
150def get_board_from_afe(hostname, afe):
151 """Retrieve given host's board from its labels in the AFE.
152
153 Looks for a host label of the form "board:<board>", and
154 returns the "<board>" part of the label. `None` is returned
155 if there is not a single, unique label matching the pattern.
156
157 @param hostname: hostname of given DUT.
158 @param afe: afe instance.
159 @returns board from label, or `None`.
160
161 """
162 return get_label_from_afe(hostname, constants.BOARD_PREFIX, afe)
163
164
165def get_build_from_afe(hostname, afe):
166 """Retrieve the current build for given host from the AFE.
167
168 Looks through the host's labels in the AFE to determine its build.
169
170 @param hostname: hostname of given DUT.
171 @param afe: afe instance.
172 @returns The current build or None if it could not find it or if there
173 were multiple build labels assigned to this host.
174
175 """
Simran Basi7756a0b2016-03-16 13:10:07 -0700176 for prefix in [provision.CROS_VERSION_PREFIX,
177 provision.ANDROID_BUILD_VERSION_PREFIX]:
178 build = get_label_from_afe(hostname, prefix + ':', afe)
179 if build:
180 return build
181 return None
Dan Shia1ecd5c2013-06-06 11:21:31 -0700182
183
Allen Li6a612392016-08-18 12:09:32 -0700184# TODO(fdeng): fix get_sheriffs crbug.com/483254
Fang Deng3197b392013-06-26 11:42:02 -0700185def get_sheriffs(lab_only=False):
Alex Millerdadc2c22013-07-08 15:21:21 -0700186 """
187 Polls the javascript file that holds the identity of the sheriff and
188 parses it's output to return a list of chromium sheriff email addresses.
189 The javascript file can contain the ldap of more than one sheriff, eg:
190 document.write('sheriff_one, sheriff_two').
191
Fang Deng3197b392013-06-26 11:42:02 -0700192 @param lab_only: if True, only pulls lab sheriff.
193 @return: A list of chroium.org sheriff email addresses to cc on the bug.
194 An empty list if failed to parse the javascript.
Alex Millerdadc2c22013-07-08 15:21:21 -0700195 """
196 sheriff_ids = []
Fang Deng3197b392013-06-26 11:42:02 -0700197 sheriff_js_list = _LAB_SHERIFF_JS.split(',')
198 if not lab_only:
199 sheriff_js_list.extend(_SHERIFF_JS.split(','))
200
201 for sheriff_js in sheriff_js_list:
Alex Millerdadc2c22013-07-08 15:21:21 -0700202 try:
Dan Shief31f032016-05-13 15:51:39 -0700203 url_content = utils.urlopen('%s%s'% (
Alex Millerdadc2c22013-07-08 15:21:21 -0700204 _CHROMIUM_BUILD_URL, sheriff_js)).read()
205 except (ValueError, IOError) as e:
beeps4efdf032013-09-17 11:27:14 -0700206 logging.warning('could not parse sheriff from url %s%s: %s',
207 _CHROMIUM_BUILD_URL, sheriff_js, str(e))
Paul Drewsbef578d2013-09-24 15:10:36 -0700208 except (urllib2.URLError, httplib.HTTPException) as e:
209 logging.warning('unexpected error reading from url "%s%s": %s',
210 _CHROMIUM_BUILD_URL, sheriff_js, str(e))
Alex Millerdadc2c22013-07-08 15:21:21 -0700211 else:
212 ldaps = re.search(r"document.write\('(.*)'\)", url_content)
213 if not ldaps:
beeps4efdf032013-09-17 11:27:14 -0700214 logging.warning('Could not retrieve sheriff ldaps for: %s',
215 url_content)
Alex Millerdadc2c22013-07-08 15:21:21 -0700216 continue
217 sheriff_ids += ['%s@chromium.org' % alias.replace(' ', '')
218 for alias in ldaps.group(1).split(',')]
219 return sheriff_ids
beeps46dadc92013-11-07 14:07:10 -0800220
221
222def remote_wget(source_url, dest_path, ssh_cmd):
223 """wget source_url from localhost to dest_path on remote host using ssh.
224
225 @param source_url: The complete url of the source of the package to send.
226 @param dest_path: The path on the remote host's file system where we would
227 like to store the package.
228 @param ssh_cmd: The ssh command to use in performing the remote wget.
229 """
230 wget_cmd = ("wget -O - %s | %s 'cat >%s'" %
231 (source_url, ssh_cmd, dest_path))
Dan Shief31f032016-05-13 15:51:39 -0700232 utils.run(wget_cmd)
beeps46dadc92013-11-07 14:07:10 -0800233
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800234
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800235_MAX_LAB_STATUS_ATTEMPTS = 5
236def _get_lab_status(status_url):
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800237 """Grabs the current lab status and message.
238
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800239 @returns The JSON object obtained from the given URL.
240
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800241 """
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800242 retry_waittime = 1
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800243 for _ in range(_MAX_LAB_STATUS_ATTEMPTS):
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800244 try:
245 response = urllib2.urlopen(status_url)
246 except IOError as e:
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800247 logging.debug('Error occurred when grabbing the lab status: %s.',
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800248 e)
249 time.sleep(retry_waittime)
250 continue
251 # Check for successful response code.
252 if response.getcode() == 200:
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800253 return json.load(response)
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800254 time.sleep(retry_waittime)
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800255 return None
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800256
257
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800258def _decode_lab_status(lab_status, build):
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800259 """Decode lab status, and report exceptions as needed.
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800260
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800261 Take a deserialized JSON object from the lab status page, and
262 interpret it to determine the actual lab status. Raise
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800263 exceptions as required to report when the lab is down.
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800264
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800265 @param build: build name that we want to check the status of.
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800266
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800267 @raises TestLabException Raised if a request to test for the given
268 status and build should be blocked.
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800269 """
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800270 # First check if the lab is up.
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800271 if not lab_status['general_state'] in LAB_GOOD_STATES:
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800272 raise TestLabException('Chromium OS Test Lab is closed: '
273 '%s.' % lab_status['message'])
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800274
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800275 # Check if the build we wish to use is disabled.
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800276 # Lab messages should be in the format of:
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800277 # Lab is 'status' [regex ...] (comment)
278 # If the build name matches any regex, it will be blocked.
279 build_exceptions = re.search('\[(.*)\]', lab_status['message'])
Prashanth Balasubramanianae437212014-10-27 11:17:26 -0700280 if not build_exceptions or not build:
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800281 return
282 for build_pattern in build_exceptions.group(1).split():
J. Richard Barnette7f215d32015-06-19 12:44:38 -0700283 if re.match(build_pattern, build):
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800284 raise TestLabException('Chromium OS Test Lab is closed: '
285 '%s matches %s.' % (
286 build, build_pattern))
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800287 return
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800288
289
Dan Shi94234cb2014-05-23 20:04:31 -0700290def is_in_lab():
291 """Check if current Autotest instance is in lab
292
293 @return: True if the Autotest instance is in lab.
294 """
Dan Shid37736b2016-07-06 15:10:29 -0700295 test_server_name = CONFIG.get_config_value('SERVER', 'hostname')
Dan Shi94234cb2014-05-23 20:04:31 -0700296 return test_server_name.startswith('cautotest')
297
298
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800299def check_lab_status(build):
300 """Check if the lab status allows us to schedule for a build.
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800301
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800302 Checks if the lab is down, or if testing for the requested build
303 should be blocked.
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800304
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800305 @param build: Name of the build to be scheduled for testing.
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800306
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800307 @raises TestLabException Raised if a request to test for the given
308 status and build should be blocked.
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800309
310 """
311 # Ensure we are trying to schedule on the actual lab.
Dan Shi94234cb2014-05-23 20:04:31 -0700312 if not is_in_lab():
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800313 return
314
315 # Download the lab status from its home on the web.
Dan Shid37736b2016-07-06 15:10:29 -0700316 status_url = CONFIG.get_config_value('CROS', 'lab_status_url')
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800317 json_status = _get_lab_status(status_url)
318 if json_status is None:
319 # We go ahead and say the lab is open if we can't get the status.
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700320 logging.warning('Could not get a status from %s', status_url)
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800321 return
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800322 _decode_lab_status(json_status, build)
beeps023afc62014-02-04 16:59:22 -0800323
324
325def lock_host_with_labels(afe, lock_manager, labels):
326 """Lookup and lock one host that matches the list of input labels.
327
328 @param afe: An instance of the afe class, as defined in server.frontend.
329 @param lock_manager: A lock manager capable of locking hosts, eg the
330 one defined in server.cros.host_lock_manager.
331 @param labels: A list of labels to look for on hosts.
332
333 @return: The hostname of a host matching all labels, and locked through the
334 lock_manager. The hostname will be as specified in the database the afe
335 object is associated with, i.e if it exists in afe_hosts with a .cros
336 suffix, the hostname returned will contain a .cros suffix.
337
338 @raises: error.NoEligibleHostException: If no hosts matching the list of
339 input labels are available.
340 @raises: error.TestError: If unable to lock a host matching the labels.
341 """
342 potential_hosts = afe.get_hosts(multiple_labels=labels)
343 if not potential_hosts:
344 raise error.NoEligibleHostException(
345 'No devices found with labels %s.' % labels)
346
347 # This prevents errors where a fault might seem repeatable
348 # because we lock, say, the same packet capturer for each test run.
349 random.shuffle(potential_hosts)
350 for host in potential_hosts:
351 if lock_manager.lock([host.hostname]):
352 logging.info('Locked device %s with labels %s.',
353 host.hostname, labels)
354 return host.hostname
355 else:
356 logging.info('Unable to lock device %s with labels %s.',
357 host.hostname, labels)
358
359 raise error.TestError('Could not lock a device with labels %s' % labels)
Dan Shi7e04fa82013-07-25 15:08:48 -0700360
361
362def get_test_views_from_tko(suite_job_id, tko):
363 """Get test name and result for given suite job ID.
364
365 @param suite_job_id: ID of suite job.
366 @param tko: an instance of TKO as defined in server/frontend.py.
367 @return: A dictionary of test status keyed by test name, e.g.,
368 {'dummy_Fail.Error': 'ERROR', 'dummy_Fail.NAError': 'TEST_NA'}
369 @raise: Exception when there is no test view found.
370
371 """
372 views = tko.run('get_detailed_test_views', afe_job_id=suite_job_id)
373 relevant_views = filter(job_status.view_is_relevant, views)
374 if not relevant_views:
375 raise Exception('Failed to retrieve job results.')
376
377 test_views = {}
378 for view in relevant_views:
379 test_views[view['test_name']] = view['status']
380
381 return test_views
MK Ryu35d661e2014-09-25 17:44:10 -0700382
383
MK Ryuc9c0c3f2014-10-27 14:36:01 -0700384def get_data_key(prefix, suite, build, board):
385 """
386 Constructs a key string from parameters.
387
388 @param prefix: Prefix for the generating key.
389 @param suite: a suite name. e.g., bvt-cq, bvt-inline, dummy
390 @param build: The build string. This string should have a consistent
391 format eg: x86-mario-release/R26-3570.0.0. If the format of this
392 string changes such that we can't determine build_type or branch
393 we give up and use the parametes we're sure of instead (suite,
394 board). eg:
395 1. build = x86-alex-pgo-release/R26-3570.0.0
396 branch = 26
397 build_type = pgo-release
398 2. build = lumpy-paladin/R28-3993.0.0-rc5
399 branch = 28
400 build_type = paladin
401 @param board: The board that this suite ran on.
402 @return: The key string used for a dictionary.
403 """
404 try:
405 _board, build_type, branch = ParseBuildName(build)[:3]
406 except ParseBuildNameException as e:
407 logging.error(str(e))
408 branch = 'Unknown'
409 build_type = 'Unknown'
410 else:
411 embedded_str = re.search(r'x86-\w+-(.*)', _board)
412 if embedded_str:
413 build_type = embedded_str.group(1) + '-' + build_type
414
415 data_key_dict = {
416 'prefix': prefix,
417 'board': board,
418 'branch': branch,
419 'build_type': build_type,
420 'suite': suite,
421 }
422 return ('%(prefix)s.%(board)s.%(build_type)s.%(branch)s.%(suite)s'
423 % data_key_dict)
MK Ryu83184352014-12-10 14:59:40 -0800424
425
MK Ryu2d0a3642015-01-07 15:11:19 -0800426def setup_logging(logfile=None, prefix=False):
MK Ryu83184352014-12-10 14:59:40 -0800427 """Setup basic logging with all logging info stripped.
428
429 Calls to logging will only show the message. No severity is logged.
430
431 @param logfile: If specified dump output to a file as well.
MK Ryu2d0a3642015-01-07 15:11:19 -0800432 @param prefix: Flag for log prefix. Set to True to add prefix to log
433 entries to include timestamp and log level. Default is False.
MK Ryu83184352014-12-10 14:59:40 -0800434 """
435 # Remove all existing handlers. client/common_lib/logging_config adds
436 # a StreamHandler to logger when modules are imported, e.g.,
437 # autotest_lib.client.bin.utils. A new StreamHandler will be added here to
438 # log only messages, not severity.
439 logging.getLogger().handlers = []
440
MK Ryu2d0a3642015-01-07 15:11:19 -0800441 if prefix:
442 log_format = '%(asctime)s %(levelname)-5s| %(message)s'
443 else:
444 log_format = '%(message)s'
445
MK Ryu83184352014-12-10 14:59:40 -0800446 screen_handler = logging.StreamHandler()
MK Ryu2d0a3642015-01-07 15:11:19 -0800447 screen_handler.setFormatter(logging.Formatter(log_format))
MK Ryu83184352014-12-10 14:59:40 -0800448 logging.getLogger().addHandler(screen_handler)
449 logging.getLogger().setLevel(logging.INFO)
450 if logfile:
451 file_handler = logging.FileHandler(logfile)
MK Ryu2d0a3642015-01-07 15:11:19 -0800452 file_handler.setFormatter(logging.Formatter(log_format))
MK Ryu83184352014-12-10 14:59:40 -0800453 file_handler.setLevel(logging.DEBUG)
454 logging.getLogger().addHandler(file_handler)
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -0800455
456
457def is_shard():
458 """Determines if this instance is running as a shard.
459
460 Reads the global_config value shard_hostname in the section SHARD.
461
462 @return True, if shard_hostname is set, False otherwise.
463 """
Dan Shid37736b2016-07-06 15:10:29 -0700464 hostname = CONFIG.get_config_value('SHARD', 'shard_hostname', default=None)
MK Ryu0c1a37d2015-04-30 12:00:55 -0700465 return bool(hostname)
466
467
Fang Deng0cb2a3b2015-12-10 17:59:00 -0800468def get_global_afe_hostname():
469 """Read the hostname of the global AFE from the global configuration."""
Dan Shid37736b2016-07-06 15:10:29 -0700470 return CONFIG.get_config_value('SERVER', 'global_afe_hostname')
Fang Deng0cb2a3b2015-12-10 17:59:00 -0800471
472
Fang Deng18699fe2015-12-04 16:40:27 -0800473def is_restricted_user(username):
474 """Determines if a user is in a restricted group.
475
476 User in restricted group only have access to master.
477
478 @param username: A string, representing a username.
479
480 @returns: True if the user is in a restricted group.
481 """
482 if not username:
483 return False
484
Dan Shid37736b2016-07-06 15:10:29 -0700485 restricted_groups = CONFIG.get_config_value(
Fang Deng18699fe2015-12-04 16:40:27 -0800486 'AUTOTEST_WEB', 'restricted_groups', default='').split(',')
487 for group in restricted_groups:
Fang Deng5229c852016-02-09 13:30:31 -0800488 try:
489 if group and username in grp.getgrnam(group).gr_mem:
490 return True
491 except KeyError as e:
492 logging.debug("%s is not a valid group.", group)
Fang Deng18699fe2015-12-04 16:40:27 -0800493 return False
494
495
MK Ryu0c1a37d2015-04-30 12:00:55 -0700496def get_special_task_status(is_complete, success, is_active):
497 """Get the status of a special task.
498
499 Emulate a host queue entry status for a special task
500 Although SpecialTasks are not HostQueueEntries, it is helpful to
501 the user to present similar statuses.
502
503 @param is_complete Boolean if the task is completed.
504 @param success Boolean if the task succeeded.
505 @param is_active Boolean if the task is active.
506
507 @return The status of a special task.
508 """
509 if is_complete:
510 if success:
511 return host_queue_entry_states.Status.COMPLETED
512 return host_queue_entry_states.Status.FAILED
513 if is_active:
514 return host_queue_entry_states.Status.RUNNING
515 return host_queue_entry_states.Status.QUEUED
516
517
518def get_special_task_exec_path(hostname, task_id, task_name, time_requested):
519 """Get the execution path of the SpecialTask.
520
521 This method returns different paths depending on where a
522 the task ran:
523 * Master: hosts/hostname/task_id-task_type
524 * Shard: Master_path/time_created
525 This is to work around the fact that a shard can fail independent
526 of the master, and be replaced by another shard that has the same
527 hosts. Without the time_created stamp the logs of the tasks running
528 on the second shard will clobber the logs from the first in google
529 storage, because task ids are not globally unique.
530
531 @param hostname Hostname
532 @param task_id Special task id
533 @param task_name Special task name (e.g., Verify, Repair, etc)
534 @param time_requested Special task requested time.
535
536 @return An execution path for the task.
537 """
538 results_path = 'hosts/%s/%s-%s' % (hostname, task_id, task_name.lower())
539
540 # If we do this on the master it will break backward compatibility,
541 # as there are tasks that currently don't have timestamps. If a host
542 # or job has been sent to a shard, the rpc for that host/job will
543 # be redirected to the shard, so this global_config check will happen
544 # on the shard the logs are on.
545 if not is_shard():
546 return results_path
547
548 # Generate a uid to disambiguate special task result directories
549 # in case this shard fails. The simplest uid is the job_id, however
550 # in rare cases tasks do not have jobs associated with them (eg:
551 # frontend verify), so just use the creation timestamp. The clocks
552 # between a shard and master should always be in sync. Any discrepancies
553 # will be brought to our attention in the form of job timeouts.
554 uid = time_requested.strftime('%Y%d%m%H%M%S')
555
556 # TODO: This is a hack, however it is the easiest way to achieve
557 # correctness. There is currently some debate over the future of
558 # tasks in our infrastructure and refactoring everything right
559 # now isn't worth the time.
560 return '%s/%s' % (results_path, uid)
561
562
563def get_job_tag(id, owner):
564 """Returns a string tag for a job.
565
566 @param id Job id
567 @param owner Job owner
568
569 """
570 return '%s-%s' % (id, owner)
571
572
573def get_hqe_exec_path(tag, execution_subdir):
574 """Returns a execution path to a HQE's results.
575
576 @param tag Tag string for a job associated with a HQE.
577 @param execution_subdir Execution sub-directory string of a HQE.
578
579 """
580 return os.path.join(tag, execution_subdir)
Dan Shi82997b92015-05-06 12:08:02 -0700581
582
583def is_inside_chroot():
584 """Check if the process is running inside chroot.
585
586 This is a wrapper around chromite.lib.cros_build_lib.IsInsideChroot(). The
587 method checks if cros_build_lib can be imported first.
588
589 @return: True if the process is running inside chroot or cros_build_lib
590 cannot be imported.
591
592 """
J. Richard Barnette7f215d32015-06-19 12:44:38 -0700593 return not cros_build_lib or cros_build_lib.IsInsideChroot()
Dan Shi70647ca2015-07-16 22:52:35 -0700594
595
596def parse_job_name(name):
597 """Parse job name to get information including build, board and suite etc.
598
599 Suite job created by run_suite follows the naming convention of:
600 [build]-test_suites/control.[suite]
601 For example: lumpy-release/R46-7272.0.0-test_suites/control.bvt
602 The naming convention is defined in site_rpc_interface.create_suite_job.
603
604 Test job created by suite job follows the naming convention of:
605 [build]/[suite]/[test name]
606 For example: lumpy-release/R46-7272.0.0/bvt/login_LoginSuccess
607 The naming convention is defined in
608 server/cros/dynamic_suite/tools.create_job_name
609
610 Note that pgo and chrome-perf builds will fail the method. Since lab does
611 not run test for these builds, they can be ignored.
Dan Shief31f032016-05-13 15:51:39 -0700612 Also, tests for Launch Control builds have different naming convention.
613 The build ID will be used as build_version.
Dan Shi70647ca2015-07-16 22:52:35 -0700614
615 @param name: Name of the job.
616
617 @return: A dictionary containing the test information. The keyvals include:
618 build: Name of the build, e.g., lumpy-release/R46-7272.0.0
619 build_version: The version of the build, e.g., R46-7272.0.0
620 board: Name of the board, e.g., lumpy
621 suite: Name of the test suite, e.g., bvt
622
623 """
624 info = {}
Dan Shief31f032016-05-13 15:51:39 -0700625 suite_job_regex = '([^/]*/[^/]*(?:/\d+)?)-test_suites/control\.(.*)'
626 test_job_regex = '([^/]*/[^/]*(?:/\d+)?)/([^/]+)/.*'
Dan Shi70647ca2015-07-16 22:52:35 -0700627 match = re.match(suite_job_regex, name)
628 if not match:
629 match = re.match(test_job_regex, name)
630 if match:
631 info['build'] = match.groups()[0]
632 info['suite'] = match.groups()[1]
633 info['build_version'] = info['build'].split('/')[1]
634 try:
635 info['board'], _, _, _ = ParseBuildName(info['build'])
636 except ParseBuildNameException:
Dan Shief31f032016-05-13 15:51:39 -0700637 # Try to parse it as Launch Control build
638 # Launch Control builds have name format:
639 # branch/build_target-build_type/build_id.
640 try:
641 _, target, build_id = utils.parse_launch_control_build(
642 info['build'])
643 build_target, _ = utils.parse_launch_control_target(target)
644 if build_target:
645 info['board'] = build_target
646 info['build_version'] = build_id
647 except ValueError:
648 pass
Dan Shi70647ca2015-07-16 22:52:35 -0700649 return info
Kevin Cheng3a4a57a2015-09-30 12:09:50 -0700650
651
652def add_label_detector(label_function_list, label_list=None, label=None):
653 """Decorator used to group functions together into the provided list.
654
655 This is a helper function to automatically add label functions that have
656 the label decorator. This is to help populate the class list of label
657 functions to be retrieved by the get_labels class method.
658
659 @param label_function_list: List of label detecting functions to add
660 decorated function to.
661 @param label_list: List of detectable labels to add detectable labels to.
662 (Default: None)
663 @param label: Label string that is detectable by this detection function
664 (Default: None)
665 """
666 def add_func(func):
667 """
668 @param func: The function to be added as a detector.
669 """
670 label_function_list.append(func)
671 if label and label_list is not None:
672 label_list.append(label)
673 return func
674 return add_func
Simran Basi9f364a62015-12-07 14:15:19 -0800675
676
677def verify_not_root_user():
678 """Simple function to error out if running with uid == 0"""
679 if os.getuid() == 0:
Simran Basi1bf60eb2015-12-01 16:39:29 -0800680 raise error.IllegalUser('This script can not be ran as root.')
681
682
683def get_hostname_from_machine(machine):
684 """Lookup hostname from a machine string or dict.
685
686 @returns: Machine hostname in string format.
687 """
688 hostname, _ = get_host_info_from_machine(machine)
689 return hostname
690
691
692def get_host_info_from_machine(machine):
693 """Lookup host information from a machine string or dict.
694
Kevin Cheng05ae2a42016-06-06 10:12:48 -0700695 @returns: Tuple of (hostname, afe_host)
Simran Basi1bf60eb2015-12-01 16:39:29 -0800696 """
697 if isinstance(machine, dict):
Kevin Cheng05ae2a42016-06-06 10:12:48 -0700698 return (machine['hostname'], machine['afe_host'])
Simran Basi1bf60eb2015-12-01 16:39:29 -0800699 else:
Kevin Cheng05ae2a42016-06-06 10:12:48 -0700700 return (machine, EmptyAFEHost())
701
702
703def get_afe_host_from_machine(machine):
704 """Return the afe_host from the machine dict if possible.
705
706 @returns: AFE host object.
707 """
708 _, afe_host = get_host_info_from_machine(machine)
709 return afe_host
Fang Dengf8a94e22015-12-07 13:39:13 -0800710
711
712def get_creds_abspath(creds_file):
713 """Returns the abspath of the credentials file.
714
715 If creds_file is already an absolute path, just return it.
716 Otherwise, assume it is located in the creds directory
717 specified in global_config and return the absolute path.
718
719 @param: creds_path, a path to the credentials.
720 @return: An absolute path to the credentials file.
721 """
722 if not creds_file:
723 return None
724 if os.path.isabs(creds_file):
725 return creds_file
Dan Shid37736b2016-07-06 15:10:29 -0700726 creds_dir = CONFIG.get_config_value('SERVER', 'creds_dir', default='')
Fang Dengf8a94e22015-12-07 13:39:13 -0800727 if not creds_dir or not os.path.exists(creds_dir):
728 creds_dir = common.autotest_dir
729 return os.path.join(creds_dir, creds_file)
Kevin Cheng3b111812015-12-15 11:52:08 -0800730
731
732def machine_is_testbed(machine):
733 """Checks if the machine is a testbed.
734
735 The signal we use to determine if the machine is a testbed
736 is if the host attributes contain more than 1 serial.
737
738 @param machine: is a list of dicts
739
740 @return: True if the machine is a testbed, False otherwise.
741 """
Kevin Cheng05ae2a42016-06-06 10:12:48 -0700742 _, afe_host = get_host_info_from_machine(machine)
743 return len(afe_host.attributes.get('serials', '').split(',')) > 1
Paul Hobbs20cc72a2016-08-30 16:57:05 -0700744
745
746def SetupTsMonGlobalState(*args, **kwargs):
747 """Import-safe wrap around chromite.lib.ts_mon_config's setup function.
748
749 @param *args: Args to pass through.
750 @param **kwargs: Kwargs to pass through.
751 """
752 if ts_mon_config:
753 return ts_mon_config.SetupTsMonGlobalState(*args, **kwargs)
754 else:
755 return TrivialContextManager
756
757
758@contextlib.contextmanager
759def TrivialContextManager():
760 """Context manager that does nothing."""
761 yield