blob: a040edf0da63fac404a5dba465f94a902972d9fe [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
Dan Shief31f032016-05-13 15:51:39 -070017from autotest_lib.client.common_lib import utils
beeps023afc62014-02-04 16:59:22 -080018from 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
Simran Basi7756a0b2016-03-16 13:10:07 -070021from autotest_lib.server.cros import provision
Dan Shia1ecd5c2013-06-06 11:21:31 -070022from autotest_lib.server.cros.dynamic_suite import constants
Dan Shi7e04fa82013-07-25 15:08:48 -070023from autotest_lib.server.cros.dynamic_suite import job_status
Dan Shia1ecd5c2013-06-06 11:21:31 -070024
Dan Shi82997b92015-05-06 12:08:02 -070025try:
26 from chromite.lib import cros_build_lib
27except ImportError:
28 logging.warn('Unable to import chromite.')
29 # Init the module variable to None. Access to this module can check if it
30 # is not None before making calls.
31 cros_build_lib = None
32
Dan Shia1ecd5c2013-06-06 11:21:31 -070033
Alex Millerdadc2c22013-07-08 15:21:21 -070034_SHERIFF_JS = global_config.global_config.get_config_value(
35 'NOTIFICATIONS', 'sheriffs', default='')
Fang Deng3197b392013-06-26 11:42:02 -070036_LAB_SHERIFF_JS = global_config.global_config.get_config_value(
37 'NOTIFICATIONS', 'lab_sheriffs', default='')
Alex Millerdadc2c22013-07-08 15:21:21 -070038_CHROMIUM_BUILD_URL = global_config.global_config.get_config_value(
39 'NOTIFICATIONS', 'chromium_build_url', default='')
40
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -080041LAB_GOOD_STATES = ('open', 'throttled')
42
43
J. Richard Barnetteabbe0962013-12-10 18:15:44 -080044class TestLabException(Exception):
45 """Exception raised when the Test Lab blocks a test or suite."""
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -080046 pass
47
48
49class ParseBuildNameException(Exception):
50 """Raised when ParseBuildName() cannot parse a build name."""
51 pass
52
53
Fang Dengf08814a2015-08-03 18:12:18 +000054class Singleton(type):
55 """Enforce that only one client class is instantiated per process."""
56 _instances = {}
57
58 def __call__(cls, *args, **kwargs):
59 """Fetch the instance of a class to use for subsequent calls."""
60 if cls not in cls._instances:
61 cls._instances[cls] = super(Singleton, cls).__call__(
62 *args, **kwargs)
63 return cls._instances[cls]
64
65
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -080066def ParseBuildName(name):
67 """Format a build name, given board, type, milestone, and manifest num.
68
Simran Basib7d21162014-05-21 15:26:16 -070069 @param name: a build name, e.g. 'x86-alex-release/R20-2015.0.0' or a
70 relative build name, e.g. 'x86-alex-release/LATEST'
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -080071
72 @return board: board the manifest is for, e.g. x86-alex.
73 @return type: one of 'release', 'factory', or 'firmware'
74 @return milestone: (numeric) milestone the manifest was associated with.
Simran Basib7d21162014-05-21 15:26:16 -070075 Will be None for relative build names.
76 @return manifest: manifest number, e.g. '2015.0.0'.
77 Will be None for relative build names.
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -080078
79 """
Simran Basif8f648e2014-09-09 11:40:03 -070080 match = re.match(r'(trybot-)?(?P<board>[\w-]+)-(?P<type>\w+)/'
81 r'(R(?P<milestone>\d+)-(?P<manifest>[\d.ab-]+)|LATEST)',
82 name)
83 if match and len(match.groups()) >= 5:
Simran Basib7d21162014-05-21 15:26:16 -070084 return (match.group('board'), match.group('type'),
85 match.group('milestone'), match.group('manifest'))
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -080086 raise ParseBuildNameException('%s is a malformed build name.' % name)
87
Alex Millerdadc2c22013-07-08 15:21:21 -070088
Dan Shi3d7a0e12015-10-12 11:55:45 -070089def get_labels_from_afe(hostname, label_prefix, afe):
90 """Retrieve a host's specific labels from the AFE.
91
92 Looks for the host labels that have the form <label_prefix>:<value>
93 and returns the "<value>" part of the label. None is returned
94 if there is not a label matching the pattern
95
96 @param hostname: hostname of given DUT.
97 @param label_prefix: prefix of label to be matched, e.g., |board:|
98 @param afe: afe instance.
99
100 @returns A list of labels that match the prefix or 'None'
101
102 """
103 labels = afe.get_labels(name__startswith=label_prefix,
104 host__hostname__in=[hostname])
105 if labels:
106 return [l.name.split(label_prefix, 1)[1] for l in labels]
107
108
Dan Shia1ecd5c2013-06-06 11:21:31 -0700109def get_label_from_afe(hostname, label_prefix, afe):
110 """Retrieve a host's specific label from the AFE.
111
112 Looks for a host label that has the form <label_prefix>:<value>
113 and returns the "<value>" part of the label. None is returned
114 if there is not a label matching the pattern
115
116 @param hostname: hostname of given DUT.
117 @param label_prefix: prefix of label to be matched, e.g., |board:|
118 @param afe: afe instance.
119 @returns the label that matches the prefix or 'None'
120
121 """
Dan Shi3d7a0e12015-10-12 11:55:45 -0700122 labels = get_labels_from_afe(hostname, label_prefix, afe)
Dan Shia1ecd5c2013-06-06 11:21:31 -0700123 if labels and len(labels) == 1:
Dan Shi3d7a0e12015-10-12 11:55:45 -0700124 return labels[0]
Dan Shia1ecd5c2013-06-06 11:21:31 -0700125
126
127def get_board_from_afe(hostname, afe):
128 """Retrieve given host's board from its labels in the AFE.
129
130 Looks for a host label of the form "board:<board>", and
131 returns the "<board>" part of the label. `None` is returned
132 if there is not a single, unique label matching the pattern.
133
134 @param hostname: hostname of given DUT.
135 @param afe: afe instance.
136 @returns board from label, or `None`.
137
138 """
139 return get_label_from_afe(hostname, constants.BOARD_PREFIX, afe)
140
141
142def get_build_from_afe(hostname, afe):
143 """Retrieve the current build for given host from the AFE.
144
145 Looks through the host's labels in the AFE to determine its build.
146
147 @param hostname: hostname of given DUT.
148 @param afe: afe instance.
149 @returns The current build or None if it could not find it or if there
150 were multiple build labels assigned to this host.
151
152 """
Simran Basi7756a0b2016-03-16 13:10:07 -0700153 for prefix in [provision.CROS_VERSION_PREFIX,
154 provision.ANDROID_BUILD_VERSION_PREFIX]:
155 build = get_label_from_afe(hostname, prefix + ':', afe)
156 if build:
157 return build
158 return None
Dan Shia1ecd5c2013-06-06 11:21:31 -0700159
160
Fang Deng3197b392013-06-26 11:42:02 -0700161def get_sheriffs(lab_only=False):
Alex Millerdadc2c22013-07-08 15:21:21 -0700162 """
163 Polls the javascript file that holds the identity of the sheriff and
164 parses it's output to return a list of chromium sheriff email addresses.
165 The javascript file can contain the ldap of more than one sheriff, eg:
166 document.write('sheriff_one, sheriff_two').
167
Fang Deng3197b392013-06-26 11:42:02 -0700168 @param lab_only: if True, only pulls lab sheriff.
169 @return: A list of chroium.org sheriff email addresses to cc on the bug.
170 An empty list if failed to parse the javascript.
Alex Millerdadc2c22013-07-08 15:21:21 -0700171 """
172 sheriff_ids = []
Fang Deng3197b392013-06-26 11:42:02 -0700173 sheriff_js_list = _LAB_SHERIFF_JS.split(',')
174 if not lab_only:
175 sheriff_js_list.extend(_SHERIFF_JS.split(','))
176
177 for sheriff_js in sheriff_js_list:
Alex Millerdadc2c22013-07-08 15:21:21 -0700178 try:
Dan Shief31f032016-05-13 15:51:39 -0700179 url_content = utils.urlopen('%s%s'% (
Alex Millerdadc2c22013-07-08 15:21:21 -0700180 _CHROMIUM_BUILD_URL, sheriff_js)).read()
181 except (ValueError, IOError) as e:
beeps4efdf032013-09-17 11:27:14 -0700182 logging.warning('could not parse sheriff from url %s%s: %s',
183 _CHROMIUM_BUILD_URL, sheriff_js, str(e))
Paul Drewsbef578d2013-09-24 15:10:36 -0700184 except (urllib2.URLError, httplib.HTTPException) as e:
185 logging.warning('unexpected error reading from url "%s%s": %s',
186 _CHROMIUM_BUILD_URL, sheriff_js, str(e))
Alex Millerdadc2c22013-07-08 15:21:21 -0700187 else:
188 ldaps = re.search(r"document.write\('(.*)'\)", url_content)
189 if not ldaps:
beeps4efdf032013-09-17 11:27:14 -0700190 logging.warning('Could not retrieve sheriff ldaps for: %s',
191 url_content)
Alex Millerdadc2c22013-07-08 15:21:21 -0700192 continue
193 sheriff_ids += ['%s@chromium.org' % alias.replace(' ', '')
194 for alias in ldaps.group(1).split(',')]
195 return sheriff_ids
beeps46dadc92013-11-07 14:07:10 -0800196
197
198def remote_wget(source_url, dest_path, ssh_cmd):
199 """wget source_url from localhost to dest_path on remote host using ssh.
200
201 @param source_url: The complete url of the source of the package to send.
202 @param dest_path: The path on the remote host's file system where we would
203 like to store the package.
204 @param ssh_cmd: The ssh command to use in performing the remote wget.
205 """
206 wget_cmd = ("wget -O - %s | %s 'cat >%s'" %
207 (source_url, ssh_cmd, dest_path))
Dan Shief31f032016-05-13 15:51:39 -0700208 utils.run(wget_cmd)
beeps46dadc92013-11-07 14:07:10 -0800209
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800210
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800211_MAX_LAB_STATUS_ATTEMPTS = 5
212def _get_lab_status(status_url):
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800213 """Grabs the current lab status and message.
214
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800215 @returns The JSON object obtained from the given URL.
216
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800217 """
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800218 retry_waittime = 1
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800219 for _ in range(_MAX_LAB_STATUS_ATTEMPTS):
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800220 try:
221 response = urllib2.urlopen(status_url)
222 except IOError as e:
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800223 logging.debug('Error occurred when grabbing the lab status: %s.',
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800224 e)
225 time.sleep(retry_waittime)
226 continue
227 # Check for successful response code.
228 if response.getcode() == 200:
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800229 return json.load(response)
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800230 time.sleep(retry_waittime)
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800231 return None
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800232
233
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800234def _decode_lab_status(lab_status, build):
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800235 """Decode lab status, and report exceptions as needed.
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800236
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800237 Take a deserialized JSON object from the lab status page, and
238 interpret it to determine the actual lab status. Raise
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800239 exceptions as required to report when the lab is down.
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800240
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800241 @param build: build name that we want to check the status of.
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800242
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800243 @raises TestLabException Raised if a request to test for the given
244 status and build should be blocked.
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800245 """
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800246 # First check if the lab is up.
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800247 if not lab_status['general_state'] in LAB_GOOD_STATES:
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800248 raise TestLabException('Chromium OS Test Lab is closed: '
249 '%s.' % lab_status['message'])
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800250
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800251 # Check if the build we wish to use is disabled.
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800252 # Lab messages should be in the format of:
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800253 # Lab is 'status' [regex ...] (comment)
254 # If the build name matches any regex, it will be blocked.
255 build_exceptions = re.search('\[(.*)\]', lab_status['message'])
Prashanth Balasubramanianae437212014-10-27 11:17:26 -0700256 if not build_exceptions or not build:
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800257 return
258 for build_pattern in build_exceptions.group(1).split():
J. Richard Barnette7f215d32015-06-19 12:44:38 -0700259 if re.match(build_pattern, build):
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800260 raise TestLabException('Chromium OS Test Lab is closed: '
261 '%s matches %s.' % (
262 build, build_pattern))
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800263 return
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800264
265
Dan Shi94234cb2014-05-23 20:04:31 -0700266def is_in_lab():
267 """Check if current Autotest instance is in lab
268
269 @return: True if the Autotest instance is in lab.
270 """
271 test_server_name = global_config.global_config.get_config_value(
272 'SERVER', 'hostname')
273 return test_server_name.startswith('cautotest')
274
275
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800276def check_lab_status(build):
277 """Check if the lab status allows us to schedule for a build.
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800278
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800279 Checks if the lab is down, or if testing for the requested build
280 should be blocked.
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800281
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800282 @param build: Name of the build to be scheduled for testing.
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800283
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800284 @raises TestLabException Raised if a request to test for the given
285 status and build should be blocked.
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800286
287 """
288 # Ensure we are trying to schedule on the actual lab.
Dan Shi94234cb2014-05-23 20:04:31 -0700289 if not is_in_lab():
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800290 return
291
292 # Download the lab status from its home on the web.
293 status_url = global_config.global_config.get_config_value(
294 'CROS', 'lab_status_url')
295 json_status = _get_lab_status(status_url)
296 if json_status is None:
297 # We go ahead and say the lab is open if we can't get the status.
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700298 logging.warning('Could not get a status from %s', status_url)
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800299 return
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800300 _decode_lab_status(json_status, build)
beeps023afc62014-02-04 16:59:22 -0800301
302
303def lock_host_with_labels(afe, lock_manager, labels):
304 """Lookup and lock one host that matches the list of input labels.
305
306 @param afe: An instance of the afe class, as defined in server.frontend.
307 @param lock_manager: A lock manager capable of locking hosts, eg the
308 one defined in server.cros.host_lock_manager.
309 @param labels: A list of labels to look for on hosts.
310
311 @return: The hostname of a host matching all labels, and locked through the
312 lock_manager. The hostname will be as specified in the database the afe
313 object is associated with, i.e if it exists in afe_hosts with a .cros
314 suffix, the hostname returned will contain a .cros suffix.
315
316 @raises: error.NoEligibleHostException: If no hosts matching the list of
317 input labels are available.
318 @raises: error.TestError: If unable to lock a host matching the labels.
319 """
320 potential_hosts = afe.get_hosts(multiple_labels=labels)
321 if not potential_hosts:
322 raise error.NoEligibleHostException(
323 'No devices found with labels %s.' % labels)
324
325 # This prevents errors where a fault might seem repeatable
326 # because we lock, say, the same packet capturer for each test run.
327 random.shuffle(potential_hosts)
328 for host in potential_hosts:
329 if lock_manager.lock([host.hostname]):
330 logging.info('Locked device %s with labels %s.',
331 host.hostname, labels)
332 return host.hostname
333 else:
334 logging.info('Unable to lock device %s with labels %s.',
335 host.hostname, labels)
336
337 raise error.TestError('Could not lock a device with labels %s' % labels)
Dan Shi7e04fa82013-07-25 15:08:48 -0700338
339
340def get_test_views_from_tko(suite_job_id, tko):
341 """Get test name and result for given suite job ID.
342
343 @param suite_job_id: ID of suite job.
344 @param tko: an instance of TKO as defined in server/frontend.py.
345 @return: A dictionary of test status keyed by test name, e.g.,
346 {'dummy_Fail.Error': 'ERROR', 'dummy_Fail.NAError': 'TEST_NA'}
347 @raise: Exception when there is no test view found.
348
349 """
350 views = tko.run('get_detailed_test_views', afe_job_id=suite_job_id)
351 relevant_views = filter(job_status.view_is_relevant, views)
352 if not relevant_views:
353 raise Exception('Failed to retrieve job results.')
354
355 test_views = {}
356 for view in relevant_views:
357 test_views[view['test_name']] = view['status']
358
359 return test_views
MK Ryu35d661e2014-09-25 17:44:10 -0700360
361
MK Ryuc9c0c3f2014-10-27 14:36:01 -0700362def get_data_key(prefix, suite, build, board):
363 """
364 Constructs a key string from parameters.
365
366 @param prefix: Prefix for the generating key.
367 @param suite: a suite name. e.g., bvt-cq, bvt-inline, dummy
368 @param build: The build string. This string should have a consistent
369 format eg: x86-mario-release/R26-3570.0.0. If the format of this
370 string changes such that we can't determine build_type or branch
371 we give up and use the parametes we're sure of instead (suite,
372 board). eg:
373 1. build = x86-alex-pgo-release/R26-3570.0.0
374 branch = 26
375 build_type = pgo-release
376 2. build = lumpy-paladin/R28-3993.0.0-rc5
377 branch = 28
378 build_type = paladin
379 @param board: The board that this suite ran on.
380 @return: The key string used for a dictionary.
381 """
382 try:
383 _board, build_type, branch = ParseBuildName(build)[:3]
384 except ParseBuildNameException as e:
385 logging.error(str(e))
386 branch = 'Unknown'
387 build_type = 'Unknown'
388 else:
389 embedded_str = re.search(r'x86-\w+-(.*)', _board)
390 if embedded_str:
391 build_type = embedded_str.group(1) + '-' + build_type
392
393 data_key_dict = {
394 'prefix': prefix,
395 'board': board,
396 'branch': branch,
397 'build_type': build_type,
398 'suite': suite,
399 }
400 return ('%(prefix)s.%(board)s.%(build_type)s.%(branch)s.%(suite)s'
401 % data_key_dict)
MK Ryu83184352014-12-10 14:59:40 -0800402
403
MK Ryu2d0a3642015-01-07 15:11:19 -0800404def setup_logging(logfile=None, prefix=False):
MK Ryu83184352014-12-10 14:59:40 -0800405 """Setup basic logging with all logging info stripped.
406
407 Calls to logging will only show the message. No severity is logged.
408
409 @param logfile: If specified dump output to a file as well.
MK Ryu2d0a3642015-01-07 15:11:19 -0800410 @param prefix: Flag for log prefix. Set to True to add prefix to log
411 entries to include timestamp and log level. Default is False.
MK Ryu83184352014-12-10 14:59:40 -0800412 """
413 # Remove all existing handlers. client/common_lib/logging_config adds
414 # a StreamHandler to logger when modules are imported, e.g.,
415 # autotest_lib.client.bin.utils. A new StreamHandler will be added here to
416 # log only messages, not severity.
417 logging.getLogger().handlers = []
418
MK Ryu2d0a3642015-01-07 15:11:19 -0800419 if prefix:
420 log_format = '%(asctime)s %(levelname)-5s| %(message)s'
421 else:
422 log_format = '%(message)s'
423
MK Ryu83184352014-12-10 14:59:40 -0800424 screen_handler = logging.StreamHandler()
MK Ryu2d0a3642015-01-07 15:11:19 -0800425 screen_handler.setFormatter(logging.Formatter(log_format))
MK Ryu83184352014-12-10 14:59:40 -0800426 logging.getLogger().addHandler(screen_handler)
427 logging.getLogger().setLevel(logging.INFO)
428 if logfile:
429 file_handler = logging.FileHandler(logfile)
MK Ryu2d0a3642015-01-07 15:11:19 -0800430 file_handler.setFormatter(logging.Formatter(log_format))
MK Ryu83184352014-12-10 14:59:40 -0800431 file_handler.setLevel(logging.DEBUG)
432 logging.getLogger().addHandler(file_handler)
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -0800433
434
435def is_shard():
436 """Determines if this instance is running as a shard.
437
438 Reads the global_config value shard_hostname in the section SHARD.
439
440 @return True, if shard_hostname is set, False otherwise.
441 """
442 hostname = global_config.global_config.get_config_value(
443 'SHARD', 'shard_hostname', default=None)
MK Ryu0c1a37d2015-04-30 12:00:55 -0700444 return bool(hostname)
445
446
Fang Deng0cb2a3b2015-12-10 17:59:00 -0800447def get_global_afe_hostname():
448 """Read the hostname of the global AFE from the global configuration."""
449 return global_config.global_config.get_config_value(
450 'SERVER', 'global_afe_hostname')
451
452
Fang Deng18699fe2015-12-04 16:40:27 -0800453def is_restricted_user(username):
454 """Determines if a user is in a restricted group.
455
456 User in restricted group only have access to master.
457
458 @param username: A string, representing a username.
459
460 @returns: True if the user is in a restricted group.
461 """
462 if not username:
463 return False
464
465 restricted_groups = global_config.global_config.get_config_value(
466 'AUTOTEST_WEB', 'restricted_groups', default='').split(',')
467 for group in restricted_groups:
Fang Deng5229c852016-02-09 13:30:31 -0800468 try:
469 if group and username in grp.getgrnam(group).gr_mem:
470 return True
471 except KeyError as e:
472 logging.debug("%s is not a valid group.", group)
Fang Deng18699fe2015-12-04 16:40:27 -0800473 return False
474
475
MK Ryu0c1a37d2015-04-30 12:00:55 -0700476def get_special_task_status(is_complete, success, is_active):
477 """Get the status of a special task.
478
479 Emulate a host queue entry status for a special task
480 Although SpecialTasks are not HostQueueEntries, it is helpful to
481 the user to present similar statuses.
482
483 @param is_complete Boolean if the task is completed.
484 @param success Boolean if the task succeeded.
485 @param is_active Boolean if the task is active.
486
487 @return The status of a special task.
488 """
489 if is_complete:
490 if success:
491 return host_queue_entry_states.Status.COMPLETED
492 return host_queue_entry_states.Status.FAILED
493 if is_active:
494 return host_queue_entry_states.Status.RUNNING
495 return host_queue_entry_states.Status.QUEUED
496
497
498def get_special_task_exec_path(hostname, task_id, task_name, time_requested):
499 """Get the execution path of the SpecialTask.
500
501 This method returns different paths depending on where a
502 the task ran:
503 * Master: hosts/hostname/task_id-task_type
504 * Shard: Master_path/time_created
505 This is to work around the fact that a shard can fail independent
506 of the master, and be replaced by another shard that has the same
507 hosts. Without the time_created stamp the logs of the tasks running
508 on the second shard will clobber the logs from the first in google
509 storage, because task ids are not globally unique.
510
511 @param hostname Hostname
512 @param task_id Special task id
513 @param task_name Special task name (e.g., Verify, Repair, etc)
514 @param time_requested Special task requested time.
515
516 @return An execution path for the task.
517 """
518 results_path = 'hosts/%s/%s-%s' % (hostname, task_id, task_name.lower())
519
520 # If we do this on the master it will break backward compatibility,
521 # as there are tasks that currently don't have timestamps. If a host
522 # or job has been sent to a shard, the rpc for that host/job will
523 # be redirected to the shard, so this global_config check will happen
524 # on the shard the logs are on.
525 if not is_shard():
526 return results_path
527
528 # Generate a uid to disambiguate special task result directories
529 # in case this shard fails. The simplest uid is the job_id, however
530 # in rare cases tasks do not have jobs associated with them (eg:
531 # frontend verify), so just use the creation timestamp. The clocks
532 # between a shard and master should always be in sync. Any discrepancies
533 # will be brought to our attention in the form of job timeouts.
534 uid = time_requested.strftime('%Y%d%m%H%M%S')
535
536 # TODO: This is a hack, however it is the easiest way to achieve
537 # correctness. There is currently some debate over the future of
538 # tasks in our infrastructure and refactoring everything right
539 # now isn't worth the time.
540 return '%s/%s' % (results_path, uid)
541
542
543def get_job_tag(id, owner):
544 """Returns a string tag for a job.
545
546 @param id Job id
547 @param owner Job owner
548
549 """
550 return '%s-%s' % (id, owner)
551
552
553def get_hqe_exec_path(tag, execution_subdir):
554 """Returns a execution path to a HQE's results.
555
556 @param tag Tag string for a job associated with a HQE.
557 @param execution_subdir Execution sub-directory string of a HQE.
558
559 """
560 return os.path.join(tag, execution_subdir)
Dan Shi82997b92015-05-06 12:08:02 -0700561
562
563def is_inside_chroot():
564 """Check if the process is running inside chroot.
565
566 This is a wrapper around chromite.lib.cros_build_lib.IsInsideChroot(). The
567 method checks if cros_build_lib can be imported first.
568
569 @return: True if the process is running inside chroot or cros_build_lib
570 cannot be imported.
571
572 """
J. Richard Barnette7f215d32015-06-19 12:44:38 -0700573 return not cros_build_lib or cros_build_lib.IsInsideChroot()
Dan Shi70647ca2015-07-16 22:52:35 -0700574
575
576def parse_job_name(name):
577 """Parse job name to get information including build, board and suite etc.
578
579 Suite job created by run_suite follows the naming convention of:
580 [build]-test_suites/control.[suite]
581 For example: lumpy-release/R46-7272.0.0-test_suites/control.bvt
582 The naming convention is defined in site_rpc_interface.create_suite_job.
583
584 Test job created by suite job follows the naming convention of:
585 [build]/[suite]/[test name]
586 For example: lumpy-release/R46-7272.0.0/bvt/login_LoginSuccess
587 The naming convention is defined in
588 server/cros/dynamic_suite/tools.create_job_name
589
590 Note that pgo and chrome-perf builds will fail the method. Since lab does
591 not run test for these builds, they can be ignored.
Dan Shief31f032016-05-13 15:51:39 -0700592 Also, tests for Launch Control builds have different naming convention.
593 The build ID will be used as build_version.
Dan Shi70647ca2015-07-16 22:52:35 -0700594
595 @param name: Name of the job.
596
597 @return: A dictionary containing the test information. The keyvals include:
598 build: Name of the build, e.g., lumpy-release/R46-7272.0.0
599 build_version: The version of the build, e.g., R46-7272.0.0
600 board: Name of the board, e.g., lumpy
601 suite: Name of the test suite, e.g., bvt
602
603 """
604 info = {}
Dan Shief31f032016-05-13 15:51:39 -0700605 suite_job_regex = '([^/]*/[^/]*(?:/\d+)?)-test_suites/control\.(.*)'
606 test_job_regex = '([^/]*/[^/]*(?:/\d+)?)/([^/]+)/.*'
Dan Shi70647ca2015-07-16 22:52:35 -0700607 match = re.match(suite_job_regex, name)
608 if not match:
609 match = re.match(test_job_regex, name)
610 if match:
611 info['build'] = match.groups()[0]
612 info['suite'] = match.groups()[1]
613 info['build_version'] = info['build'].split('/')[1]
614 try:
615 info['board'], _, _, _ = ParseBuildName(info['build'])
616 except ParseBuildNameException:
Dan Shief31f032016-05-13 15:51:39 -0700617 # Try to parse it as Launch Control build
618 # Launch Control builds have name format:
619 # branch/build_target-build_type/build_id.
620 try:
621 _, target, build_id = utils.parse_launch_control_build(
622 info['build'])
623 build_target, _ = utils.parse_launch_control_target(target)
624 if build_target:
625 info['board'] = build_target
626 info['build_version'] = build_id
627 except ValueError:
628 pass
Dan Shi70647ca2015-07-16 22:52:35 -0700629 return info
Kevin Cheng3a4a57a2015-09-30 12:09:50 -0700630
631
632def add_label_detector(label_function_list, label_list=None, label=None):
633 """Decorator used to group functions together into the provided list.
634
635 This is a helper function to automatically add label functions that have
636 the label decorator. This is to help populate the class list of label
637 functions to be retrieved by the get_labels class method.
638
639 @param label_function_list: List of label detecting functions to add
640 decorated function to.
641 @param label_list: List of detectable labels to add detectable labels to.
642 (Default: None)
643 @param label: Label string that is detectable by this detection function
644 (Default: None)
645 """
646 def add_func(func):
647 """
648 @param func: The function to be added as a detector.
649 """
650 label_function_list.append(func)
651 if label and label_list is not None:
652 label_list.append(label)
653 return func
654 return add_func
Simran Basi9f364a62015-12-07 14:15:19 -0800655
656
657def verify_not_root_user():
658 """Simple function to error out if running with uid == 0"""
659 if os.getuid() == 0:
Simran Basi1bf60eb2015-12-01 16:39:29 -0800660 raise error.IllegalUser('This script can not be ran as root.')
661
662
663def get_hostname_from_machine(machine):
664 """Lookup hostname from a machine string or dict.
665
666 @returns: Machine hostname in string format.
667 """
668 hostname, _ = get_host_info_from_machine(machine)
669 return hostname
670
671
672def get_host_info_from_machine(machine):
673 """Lookup host information from a machine string or dict.
674
675 @returns: Tuple of (hostname, host_attributes)
676 """
677 if isinstance(machine, dict):
678 return (machine['hostname'], machine['host_attributes'])
679 else:
680 return (machine, {})
Fang Dengf8a94e22015-12-07 13:39:13 -0800681
682
683def get_creds_abspath(creds_file):
684 """Returns the abspath of the credentials file.
685
686 If creds_file is already an absolute path, just return it.
687 Otherwise, assume it is located in the creds directory
688 specified in global_config and return the absolute path.
689
690 @param: creds_path, a path to the credentials.
691 @return: An absolute path to the credentials file.
692 """
693 if not creds_file:
694 return None
695 if os.path.isabs(creds_file):
696 return creds_file
697 creds_dir = global_config.global_config.get_config_value(
698 'SERVER', 'creds_dir', default='')
699 if not creds_dir or not os.path.exists(creds_dir):
700 creds_dir = common.autotest_dir
701 return os.path.join(creds_dir, creds_file)
Kevin Cheng3b111812015-12-15 11:52:08 -0800702
703
704def machine_is_testbed(machine):
705 """Checks if the machine is a testbed.
706
707 The signal we use to determine if the machine is a testbed
708 is if the host attributes contain more than 1 serial.
709
710 @param machine: is a list of dicts
711
712 @return: True if the machine is a testbed, False otherwise.
713 """
714 _, attributes = get_host_info_from_machine(machine)
715 if len(attributes.get('serials', '').split(',')) > 1:
716 return True
717 return False