blob: 52ab7914d6e3e273c37b9b2dc94ab542dbcf4a47 [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
Fang Deng18699fe2015-12-04 16:40:27 -08006import grp
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -08007import httplib
8import json
Alex Millerdadc2c22013-07-08 15:21:21 -07009import logging
MK Ryu35d661e2014-09-25 17:44:10 -070010import os
beeps023afc62014-02-04 16:59:22 -080011import random
Alex Millerdadc2c22013-07-08 15:21:21 -070012import re
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -080013import time
Paul Drewsbef578d2013-09-24 15:10:36 -070014import urllib2
Alex Millerdadc2c22013-07-08 15:21:21 -070015
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -080016import common
beeps023afc62014-02-04 16:59:22 -080017from autotest_lib.client.common_lib import base_utils
18from autotest_lib.client.common_lib import error
19from autotest_lib.client.common_lib import global_config
MK Ryu0c1a37d2015-04-30 12:00:55 -070020from autotest_lib.client.common_lib import host_queue_entry_states
Dan Shia1ecd5c2013-06-06 11:21:31 -070021from autotest_lib.server.cros.dynamic_suite import constants
Dan Shi7e04fa82013-07-25 15:08:48 -070022from autotest_lib.server.cros.dynamic_suite import job_status
Dan Shia1ecd5c2013-06-06 11:21:31 -070023
Dan Shi82997b92015-05-06 12:08:02 -070024try:
25 from chromite.lib import cros_build_lib
26except ImportError:
27 logging.warn('Unable to import chromite.')
28 # Init the module variable to None. Access to this module can check if it
29 # is not None before making calls.
30 cros_build_lib = None
31
Dan Shia1ecd5c2013-06-06 11:21:31 -070032
Alex Millerdadc2c22013-07-08 15:21:21 -070033_SHERIFF_JS = global_config.global_config.get_config_value(
34 'NOTIFICATIONS', 'sheriffs', default='')
Fang Deng3197b392013-06-26 11:42:02 -070035_LAB_SHERIFF_JS = global_config.global_config.get_config_value(
36 'NOTIFICATIONS', 'lab_sheriffs', default='')
Alex Millerdadc2c22013-07-08 15:21:21 -070037_CHROMIUM_BUILD_URL = global_config.global_config.get_config_value(
38 'NOTIFICATIONS', 'chromium_build_url', default='')
39
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -080040LAB_GOOD_STATES = ('open', 'throttled')
41
42
J. Richard Barnetteabbe0962013-12-10 18:15:44 -080043class TestLabException(Exception):
44 """Exception raised when the Test Lab blocks a test or suite."""
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -080045 pass
46
47
48class ParseBuildNameException(Exception):
49 """Raised when ParseBuildName() cannot parse a build name."""
50 pass
51
52
Fang Dengf08814a2015-08-03 18:12:18 +000053class Singleton(type):
54 """Enforce that only one client class is instantiated per process."""
55 _instances = {}
56
57 def __call__(cls, *args, **kwargs):
58 """Fetch the instance of a class to use for subsequent calls."""
59 if cls not in cls._instances:
60 cls._instances[cls] = super(Singleton, cls).__call__(
61 *args, **kwargs)
62 return cls._instances[cls]
63
64
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -080065def ParseBuildName(name):
66 """Format a build name, given board, type, milestone, and manifest num.
67
Simran Basib7d21162014-05-21 15:26:16 -070068 @param name: a build name, e.g. 'x86-alex-release/R20-2015.0.0' or a
69 relative build name, e.g. 'x86-alex-release/LATEST'
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -080070
71 @return board: board the manifest is for, e.g. x86-alex.
72 @return type: one of 'release', 'factory', or 'firmware'
73 @return milestone: (numeric) milestone the manifest was associated with.
Simran Basib7d21162014-05-21 15:26:16 -070074 Will be None for relative build names.
75 @return manifest: manifest number, e.g. '2015.0.0'.
76 Will be None for relative build names.
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -080077
78 """
Simran Basif8f648e2014-09-09 11:40:03 -070079 match = re.match(r'(trybot-)?(?P<board>[\w-]+)-(?P<type>\w+)/'
80 r'(R(?P<milestone>\d+)-(?P<manifest>[\d.ab-]+)|LATEST)',
81 name)
82 if match and len(match.groups()) >= 5:
Simran Basib7d21162014-05-21 15:26:16 -070083 return (match.group('board'), match.group('type'),
84 match.group('milestone'), match.group('manifest'))
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -080085 raise ParseBuildNameException('%s is a malformed build name.' % name)
86
Alex Millerdadc2c22013-07-08 15:21:21 -070087
Dan Shi3d7a0e12015-10-12 11:55:45 -070088def get_labels_from_afe(hostname, label_prefix, afe):
89 """Retrieve a host's specific labels from the AFE.
90
91 Looks for the host labels that have the form <label_prefix>:<value>
92 and returns the "<value>" part of the label. None is returned
93 if there is not a label matching the pattern
94
95 @param hostname: hostname of given DUT.
96 @param label_prefix: prefix of label to be matched, e.g., |board:|
97 @param afe: afe instance.
98
99 @returns A list of labels that match the prefix or 'None'
100
101 """
102 labels = afe.get_labels(name__startswith=label_prefix,
103 host__hostname__in=[hostname])
104 if labels:
105 return [l.name.split(label_prefix, 1)[1] for l in labels]
106
107
Dan Shia1ecd5c2013-06-06 11:21:31 -0700108def get_label_from_afe(hostname, label_prefix, afe):
109 """Retrieve a host's specific label from the AFE.
110
111 Looks for a host label that has the form <label_prefix>:<value>
112 and returns the "<value>" part of the label. None is returned
113 if there is not a label matching the pattern
114
115 @param hostname: hostname of given DUT.
116 @param label_prefix: prefix of label to be matched, e.g., |board:|
117 @param afe: afe instance.
118 @returns the label that matches the prefix or 'None'
119
120 """
Dan Shi3d7a0e12015-10-12 11:55:45 -0700121 labels = get_labels_from_afe(hostname, label_prefix, afe)
Dan Shia1ecd5c2013-06-06 11:21:31 -0700122 if labels and len(labels) == 1:
Dan Shi3d7a0e12015-10-12 11:55:45 -0700123 return labels[0]
Dan Shia1ecd5c2013-06-06 11:21:31 -0700124
125
126def get_board_from_afe(hostname, afe):
127 """Retrieve given host's board from its labels in the AFE.
128
129 Looks for a host label of the form "board:<board>", and
130 returns the "<board>" part of the label. `None` is returned
131 if there is not a single, unique label matching the pattern.
132
133 @param hostname: hostname of given DUT.
134 @param afe: afe instance.
135 @returns board from label, or `None`.
136
137 """
138 return get_label_from_afe(hostname, constants.BOARD_PREFIX, afe)
139
140
141def get_build_from_afe(hostname, afe):
142 """Retrieve the current build for given host from the AFE.
143
144 Looks through the host's labels in the AFE to determine its build.
145
146 @param hostname: hostname of given DUT.
147 @param afe: afe instance.
148 @returns The current build or None if it could not find it or if there
149 were multiple build labels assigned to this host.
150
151 """
152 return get_label_from_afe(hostname, constants.VERSION_PREFIX, afe)
153
154
Fang Deng3197b392013-06-26 11:42:02 -0700155def get_sheriffs(lab_only=False):
Alex Millerdadc2c22013-07-08 15:21:21 -0700156 """
157 Polls the javascript file that holds the identity of the sheriff and
158 parses it's output to return a list of chromium sheriff email addresses.
159 The javascript file can contain the ldap of more than one sheriff, eg:
160 document.write('sheriff_one, sheriff_two').
161
Fang Deng3197b392013-06-26 11:42:02 -0700162 @param lab_only: if True, only pulls lab sheriff.
163 @return: A list of chroium.org sheriff email addresses to cc on the bug.
164 An empty list if failed to parse the javascript.
Alex Millerdadc2c22013-07-08 15:21:21 -0700165 """
166 sheriff_ids = []
Fang Deng3197b392013-06-26 11:42:02 -0700167 sheriff_js_list = _LAB_SHERIFF_JS.split(',')
168 if not lab_only:
169 sheriff_js_list.extend(_SHERIFF_JS.split(','))
170
171 for sheriff_js in sheriff_js_list:
Alex Millerdadc2c22013-07-08 15:21:21 -0700172 try:
173 url_content = base_utils.urlopen('%s%s'% (
174 _CHROMIUM_BUILD_URL, sheriff_js)).read()
175 except (ValueError, IOError) as e:
beeps4efdf032013-09-17 11:27:14 -0700176 logging.warning('could not parse sheriff from url %s%s: %s',
177 _CHROMIUM_BUILD_URL, sheriff_js, str(e))
Paul Drewsbef578d2013-09-24 15:10:36 -0700178 except (urllib2.URLError, httplib.HTTPException) as e:
179 logging.warning('unexpected error reading from url "%s%s": %s',
180 _CHROMIUM_BUILD_URL, sheriff_js, str(e))
Alex Millerdadc2c22013-07-08 15:21:21 -0700181 else:
182 ldaps = re.search(r"document.write\('(.*)'\)", url_content)
183 if not ldaps:
beeps4efdf032013-09-17 11:27:14 -0700184 logging.warning('Could not retrieve sheriff ldaps for: %s',
185 url_content)
Alex Millerdadc2c22013-07-08 15:21:21 -0700186 continue
187 sheriff_ids += ['%s@chromium.org' % alias.replace(' ', '')
188 for alias in ldaps.group(1).split(',')]
189 return sheriff_ids
beeps46dadc92013-11-07 14:07:10 -0800190
191
192def remote_wget(source_url, dest_path, ssh_cmd):
193 """wget source_url from localhost to dest_path on remote host using ssh.
194
195 @param source_url: The complete url of the source of the package to send.
196 @param dest_path: The path on the remote host's file system where we would
197 like to store the package.
198 @param ssh_cmd: The ssh command to use in performing the remote wget.
199 """
200 wget_cmd = ("wget -O - %s | %s 'cat >%s'" %
201 (source_url, ssh_cmd, dest_path))
202 base_utils.run(wget_cmd)
203
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800204
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800205_MAX_LAB_STATUS_ATTEMPTS = 5
206def _get_lab_status(status_url):
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800207 """Grabs the current lab status and message.
208
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800209 @returns The JSON object obtained from the given URL.
210
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800211 """
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800212 retry_waittime = 1
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800213 for _ in range(_MAX_LAB_STATUS_ATTEMPTS):
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800214 try:
215 response = urllib2.urlopen(status_url)
216 except IOError as e:
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800217 logging.debug('Error occurred when grabbing the lab status: %s.',
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800218 e)
219 time.sleep(retry_waittime)
220 continue
221 # Check for successful response code.
222 if response.getcode() == 200:
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800223 return json.load(response)
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800224 time.sleep(retry_waittime)
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800225 return None
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800226
227
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800228def _decode_lab_status(lab_status, build):
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800229 """Decode lab status, and report exceptions as needed.
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800230
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800231 Take a deserialized JSON object from the lab status page, and
232 interpret it to determine the actual lab status. Raise
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800233 exceptions as required to report when the lab is down.
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800234
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800235 @param build: build name that we want to check the status of.
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800236
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800237 @raises TestLabException Raised if a request to test for the given
238 status and build should be blocked.
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800239 """
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800240 # First check if the lab is up.
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800241 if not lab_status['general_state'] in LAB_GOOD_STATES:
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800242 raise TestLabException('Chromium OS Test Lab is closed: '
243 '%s.' % lab_status['message'])
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800244
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800245 # Check if the build we wish to use is disabled.
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800246 # Lab messages should be in the format of:
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800247 # Lab is 'status' [regex ...] (comment)
248 # If the build name matches any regex, it will be blocked.
249 build_exceptions = re.search('\[(.*)\]', lab_status['message'])
Prashanth Balasubramanianae437212014-10-27 11:17:26 -0700250 if not build_exceptions or not build:
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800251 return
252 for build_pattern in build_exceptions.group(1).split():
J. Richard Barnette7f215d32015-06-19 12:44:38 -0700253 if re.match(build_pattern, build):
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800254 raise TestLabException('Chromium OS Test Lab is closed: '
255 '%s matches %s.' % (
256 build, build_pattern))
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800257 return
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800258
259
Dan Shi94234cb2014-05-23 20:04:31 -0700260def is_in_lab():
261 """Check if current Autotest instance is in lab
262
263 @return: True if the Autotest instance is in lab.
264 """
265 test_server_name = global_config.global_config.get_config_value(
266 'SERVER', 'hostname')
267 return test_server_name.startswith('cautotest')
268
269
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800270def check_lab_status(build):
271 """Check if the lab status allows us to schedule for a build.
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800272
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800273 Checks if the lab is down, or if testing for the requested build
274 should be blocked.
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800275
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800276 @param build: Name of the build to be scheduled for testing.
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800277
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800278 @raises TestLabException Raised if a request to test for the given
279 status and build should be blocked.
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800280
281 """
282 # Ensure we are trying to schedule on the actual lab.
Dan Shi94234cb2014-05-23 20:04:31 -0700283 if not is_in_lab():
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800284 return
285
286 # Download the lab status from its home on the web.
287 status_url = global_config.global_config.get_config_value(
288 'CROS', 'lab_status_url')
289 json_status = _get_lab_status(status_url)
290 if json_status is None:
291 # We go ahead and say the lab is open if we can't get the status.
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700292 logging.warning('Could not get a status from %s', status_url)
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800293 return
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800294 _decode_lab_status(json_status, build)
beeps023afc62014-02-04 16:59:22 -0800295
296
297def lock_host_with_labels(afe, lock_manager, labels):
298 """Lookup and lock one host that matches the list of input labels.
299
300 @param afe: An instance of the afe class, as defined in server.frontend.
301 @param lock_manager: A lock manager capable of locking hosts, eg the
302 one defined in server.cros.host_lock_manager.
303 @param labels: A list of labels to look for on hosts.
304
305 @return: The hostname of a host matching all labels, and locked through the
306 lock_manager. The hostname will be as specified in the database the afe
307 object is associated with, i.e if it exists in afe_hosts with a .cros
308 suffix, the hostname returned will contain a .cros suffix.
309
310 @raises: error.NoEligibleHostException: If no hosts matching the list of
311 input labels are available.
312 @raises: error.TestError: If unable to lock a host matching the labels.
313 """
314 potential_hosts = afe.get_hosts(multiple_labels=labels)
315 if not potential_hosts:
316 raise error.NoEligibleHostException(
317 'No devices found with labels %s.' % labels)
318
319 # This prevents errors where a fault might seem repeatable
320 # because we lock, say, the same packet capturer for each test run.
321 random.shuffle(potential_hosts)
322 for host in potential_hosts:
323 if lock_manager.lock([host.hostname]):
324 logging.info('Locked device %s with labels %s.',
325 host.hostname, labels)
326 return host.hostname
327 else:
328 logging.info('Unable to lock device %s with labels %s.',
329 host.hostname, labels)
330
331 raise error.TestError('Could not lock a device with labels %s' % labels)
Dan Shi7e04fa82013-07-25 15:08:48 -0700332
333
334def get_test_views_from_tko(suite_job_id, tko):
335 """Get test name and result for given suite job ID.
336
337 @param suite_job_id: ID of suite job.
338 @param tko: an instance of TKO as defined in server/frontend.py.
339 @return: A dictionary of test status keyed by test name, e.g.,
340 {'dummy_Fail.Error': 'ERROR', 'dummy_Fail.NAError': 'TEST_NA'}
341 @raise: Exception when there is no test view found.
342
343 """
344 views = tko.run('get_detailed_test_views', afe_job_id=suite_job_id)
345 relevant_views = filter(job_status.view_is_relevant, views)
346 if not relevant_views:
347 raise Exception('Failed to retrieve job results.')
348
349 test_views = {}
350 for view in relevant_views:
351 test_views[view['test_name']] = view['status']
352
353 return test_views
MK Ryu35d661e2014-09-25 17:44:10 -0700354
355
356def parse_simple_config(config_file):
357 """Get paths by parsing a simple config file.
358
359 Each line of the config file is a path for a file or directory.
360 Ignore an empty line and a line starting with a hash character ('#').
361 One example of this kind of simple config file is
362 client/common_lib/logs_to_collect.
363
364 @param config_file: Config file path
365 @return: A list of directory strings
366 """
367 dirs = []
368 for l in open(config_file):
369 l = l.strip()
370 if l and not l.startswith('#'):
371 dirs.append(l)
372 return dirs
373
374
375def concat_path_except_last(base, sub):
376 """Concatenate two paths but exclude last entry.
377
378 Take two paths as parameters and return a path string in which
379 the second path becomes under the first path.
380 In addition, remove the last path entry from the concatenated path.
381 This works even when two paths are absolute paths.
382
383 e.g., /usr/local/autotest/results/ + /var/log/ =
384 /usr/local/autotest/results/var
385
386 e.g., /usr/local/autotest/results/ + /var/log/syslog =
387 /usr/local/autotest/results/var/log
388
389 @param base: Beginning path
390 @param sub: The path that is concatenated to base
391 @return: Concatenated path string
392 """
393 dirname = os.path.dirname(sub.rstrip('/'))
394 return os.path.join(base, dirname.strip('/'))
MK Ryuc9c0c3f2014-10-27 14:36:01 -0700395
396
397def get_data_key(prefix, suite, build, board):
398 """
399 Constructs a key string from parameters.
400
401 @param prefix: Prefix for the generating key.
402 @param suite: a suite name. e.g., bvt-cq, bvt-inline, dummy
403 @param build: The build string. This string should have a consistent
404 format eg: x86-mario-release/R26-3570.0.0. If the format of this
405 string changes such that we can't determine build_type or branch
406 we give up and use the parametes we're sure of instead (suite,
407 board). eg:
408 1. build = x86-alex-pgo-release/R26-3570.0.0
409 branch = 26
410 build_type = pgo-release
411 2. build = lumpy-paladin/R28-3993.0.0-rc5
412 branch = 28
413 build_type = paladin
414 @param board: The board that this suite ran on.
415 @return: The key string used for a dictionary.
416 """
417 try:
418 _board, build_type, branch = ParseBuildName(build)[:3]
419 except ParseBuildNameException as e:
420 logging.error(str(e))
421 branch = 'Unknown'
422 build_type = 'Unknown'
423 else:
424 embedded_str = re.search(r'x86-\w+-(.*)', _board)
425 if embedded_str:
426 build_type = embedded_str.group(1) + '-' + build_type
427
428 data_key_dict = {
429 'prefix': prefix,
430 'board': board,
431 'branch': branch,
432 'build_type': build_type,
433 'suite': suite,
434 }
435 return ('%(prefix)s.%(board)s.%(build_type)s.%(branch)s.%(suite)s'
436 % data_key_dict)
MK Ryu83184352014-12-10 14:59:40 -0800437
438
MK Ryu2d0a3642015-01-07 15:11:19 -0800439def setup_logging(logfile=None, prefix=False):
MK Ryu83184352014-12-10 14:59:40 -0800440 """Setup basic logging with all logging info stripped.
441
442 Calls to logging will only show the message. No severity is logged.
443
444 @param logfile: If specified dump output to a file as well.
MK Ryu2d0a3642015-01-07 15:11:19 -0800445 @param prefix: Flag for log prefix. Set to True to add prefix to log
446 entries to include timestamp and log level. Default is False.
MK Ryu83184352014-12-10 14:59:40 -0800447 """
448 # Remove all existing handlers. client/common_lib/logging_config adds
449 # a StreamHandler to logger when modules are imported, e.g.,
450 # autotest_lib.client.bin.utils. A new StreamHandler will be added here to
451 # log only messages, not severity.
452 logging.getLogger().handlers = []
453
MK Ryu2d0a3642015-01-07 15:11:19 -0800454 if prefix:
455 log_format = '%(asctime)s %(levelname)-5s| %(message)s'
456 else:
457 log_format = '%(message)s'
458
MK Ryu83184352014-12-10 14:59:40 -0800459 screen_handler = logging.StreamHandler()
MK Ryu2d0a3642015-01-07 15:11:19 -0800460 screen_handler.setFormatter(logging.Formatter(log_format))
MK Ryu83184352014-12-10 14:59:40 -0800461 logging.getLogger().addHandler(screen_handler)
462 logging.getLogger().setLevel(logging.INFO)
463 if logfile:
464 file_handler = logging.FileHandler(logfile)
MK Ryu2d0a3642015-01-07 15:11:19 -0800465 file_handler.setFormatter(logging.Formatter(log_format))
MK Ryu83184352014-12-10 14:59:40 -0800466 file_handler.setLevel(logging.DEBUG)
467 logging.getLogger().addHandler(file_handler)
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -0800468
469
470def is_shard():
471 """Determines if this instance is running as a shard.
472
473 Reads the global_config value shard_hostname in the section SHARD.
474
475 @return True, if shard_hostname is set, False otherwise.
476 """
477 hostname = global_config.global_config.get_config_value(
478 'SHARD', 'shard_hostname', default=None)
MK Ryu0c1a37d2015-04-30 12:00:55 -0700479 return bool(hostname)
480
481
Fang Deng0cb2a3b2015-12-10 17:59:00 -0800482def get_global_afe_hostname():
483 """Read the hostname of the global AFE from the global configuration."""
484 return global_config.global_config.get_config_value(
485 'SERVER', 'global_afe_hostname')
486
487
Fang Deng18699fe2015-12-04 16:40:27 -0800488def is_restricted_user(username):
489 """Determines if a user is in a restricted group.
490
491 User in restricted group only have access to master.
492
493 @param username: A string, representing a username.
494
495 @returns: True if the user is in a restricted group.
496 """
497 if not username:
498 return False
499
500 restricted_groups = global_config.global_config.get_config_value(
501 'AUTOTEST_WEB', 'restricted_groups', default='').split(',')
502 for group in restricted_groups:
503 if group and username in grp.getgrnam(group).gr_mem:
504 return True
505 return False
506
507
MK Ryu0c1a37d2015-04-30 12:00:55 -0700508def get_special_task_status(is_complete, success, is_active):
509 """Get the status of a special task.
510
511 Emulate a host queue entry status for a special task
512 Although SpecialTasks are not HostQueueEntries, it is helpful to
513 the user to present similar statuses.
514
515 @param is_complete Boolean if the task is completed.
516 @param success Boolean if the task succeeded.
517 @param is_active Boolean if the task is active.
518
519 @return The status of a special task.
520 """
521 if is_complete:
522 if success:
523 return host_queue_entry_states.Status.COMPLETED
524 return host_queue_entry_states.Status.FAILED
525 if is_active:
526 return host_queue_entry_states.Status.RUNNING
527 return host_queue_entry_states.Status.QUEUED
528
529
530def get_special_task_exec_path(hostname, task_id, task_name, time_requested):
531 """Get the execution path of the SpecialTask.
532
533 This method returns different paths depending on where a
534 the task ran:
535 * Master: hosts/hostname/task_id-task_type
536 * Shard: Master_path/time_created
537 This is to work around the fact that a shard can fail independent
538 of the master, and be replaced by another shard that has the same
539 hosts. Without the time_created stamp the logs of the tasks running
540 on the second shard will clobber the logs from the first in google
541 storage, because task ids are not globally unique.
542
543 @param hostname Hostname
544 @param task_id Special task id
545 @param task_name Special task name (e.g., Verify, Repair, etc)
546 @param time_requested Special task requested time.
547
548 @return An execution path for the task.
549 """
550 results_path = 'hosts/%s/%s-%s' % (hostname, task_id, task_name.lower())
551
552 # If we do this on the master it will break backward compatibility,
553 # as there are tasks that currently don't have timestamps. If a host
554 # or job has been sent to a shard, the rpc for that host/job will
555 # be redirected to the shard, so this global_config check will happen
556 # on the shard the logs are on.
557 if not is_shard():
558 return results_path
559
560 # Generate a uid to disambiguate special task result directories
561 # in case this shard fails. The simplest uid is the job_id, however
562 # in rare cases tasks do not have jobs associated with them (eg:
563 # frontend verify), so just use the creation timestamp. The clocks
564 # between a shard and master should always be in sync. Any discrepancies
565 # will be brought to our attention in the form of job timeouts.
566 uid = time_requested.strftime('%Y%d%m%H%M%S')
567
568 # TODO: This is a hack, however it is the easiest way to achieve
569 # correctness. There is currently some debate over the future of
570 # tasks in our infrastructure and refactoring everything right
571 # now isn't worth the time.
572 return '%s/%s' % (results_path, uid)
573
574
575def get_job_tag(id, owner):
576 """Returns a string tag for a job.
577
578 @param id Job id
579 @param owner Job owner
580
581 """
582 return '%s-%s' % (id, owner)
583
584
585def get_hqe_exec_path(tag, execution_subdir):
586 """Returns a execution path to a HQE's results.
587
588 @param tag Tag string for a job associated with a HQE.
589 @param execution_subdir Execution sub-directory string of a HQE.
590
591 """
592 return os.path.join(tag, execution_subdir)
Dan Shi82997b92015-05-06 12:08:02 -0700593
594
595def is_inside_chroot():
596 """Check if the process is running inside chroot.
597
598 This is a wrapper around chromite.lib.cros_build_lib.IsInsideChroot(). The
599 method checks if cros_build_lib can be imported first.
600
601 @return: True if the process is running inside chroot or cros_build_lib
602 cannot be imported.
603
604 """
J. Richard Barnette7f215d32015-06-19 12:44:38 -0700605 return not cros_build_lib or cros_build_lib.IsInsideChroot()
Dan Shi70647ca2015-07-16 22:52:35 -0700606
607
608def parse_job_name(name):
609 """Parse job name to get information including build, board and suite etc.
610
611 Suite job created by run_suite follows the naming convention of:
612 [build]-test_suites/control.[suite]
613 For example: lumpy-release/R46-7272.0.0-test_suites/control.bvt
614 The naming convention is defined in site_rpc_interface.create_suite_job.
615
616 Test job created by suite job follows the naming convention of:
617 [build]/[suite]/[test name]
618 For example: lumpy-release/R46-7272.0.0/bvt/login_LoginSuccess
619 The naming convention is defined in
620 server/cros/dynamic_suite/tools.create_job_name
621
622 Note that pgo and chrome-perf builds will fail the method. Since lab does
623 not run test for these builds, they can be ignored.
624
625 @param name: Name of the job.
626
627 @return: A dictionary containing the test information. The keyvals include:
628 build: Name of the build, e.g., lumpy-release/R46-7272.0.0
629 build_version: The version of the build, e.g., R46-7272.0.0
630 board: Name of the board, e.g., lumpy
631 suite: Name of the test suite, e.g., bvt
632
633 """
634 info = {}
635 suite_job_regex = '([^/]*/[^/]*)-test_suites/control\.(.*)'
636 test_job_regex = '([^/]*/[^/]*)/([^/]+)/.*'
637 match = re.match(suite_job_regex, name)
638 if not match:
639 match = re.match(test_job_regex, name)
640 if match:
641 info['build'] = match.groups()[0]
642 info['suite'] = match.groups()[1]
643 info['build_version'] = info['build'].split('/')[1]
644 try:
645 info['board'], _, _, _ = ParseBuildName(info['build'])
646 except ParseBuildNameException:
647 pass
648 return info
Kevin Cheng3a4a57a2015-09-30 12:09:50 -0700649
650
651def add_label_detector(label_function_list, label_list=None, label=None):
652 """Decorator used to group functions together into the provided list.
653
654 This is a helper function to automatically add label functions that have
655 the label decorator. This is to help populate the class list of label
656 functions to be retrieved by the get_labels class method.
657
658 @param label_function_list: List of label detecting functions to add
659 decorated function to.
660 @param label_list: List of detectable labels to add detectable labels to.
661 (Default: None)
662 @param label: Label string that is detectable by this detection function
663 (Default: None)
664 """
665 def add_func(func):
666 """
667 @param func: The function to be added as a detector.
668 """
669 label_function_list.append(func)
670 if label and label_list is not None:
671 label_list.append(label)
672 return func
673 return add_func
Simran Basi9f364a62015-12-07 14:15:19 -0800674
675
676def verify_not_root_user():
677 """Simple function to error out if running with uid == 0"""
678 if os.getuid() == 0:
Simran Basi1bf60eb2015-12-01 16:39:29 -0800679 raise error.IllegalUser('This script can not be ran as root.')
680
681
682def get_hostname_from_machine(machine):
683 """Lookup hostname from a machine string or dict.
684
685 @returns: Machine hostname in string format.
686 """
687 hostname, _ = get_host_info_from_machine(machine)
688 return hostname
689
690
691def get_host_info_from_machine(machine):
692 """Lookup host information from a machine string or dict.
693
694 @returns: Tuple of (hostname, host_attributes)
695 """
696 if isinstance(machine, dict):
697 return (machine['hostname'], machine['host_attributes'])
698 else:
699 return (machine, {})
Fang Dengf8a94e22015-12-07 13:39:13 -0800700
701
702def get_creds_abspath(creds_file):
703 """Returns the abspath of the credentials file.
704
705 If creds_file is already an absolute path, just return it.
706 Otherwise, assume it is located in the creds directory
707 specified in global_config and return the absolute path.
708
709 @param: creds_path, a path to the credentials.
710 @return: An absolute path to the credentials file.
711 """
712 if not creds_file:
713 return None
714 if os.path.isabs(creds_file):
715 return creds_file
716 creds_dir = global_config.global_config.get_config_value(
717 'SERVER', 'creds_dir', default='')
718 if not creds_dir or not os.path.exists(creds_dir):
719 creds_dir = common.autotest_dir
720 return os.path.join(creds_dir, creds_file)