blob: 804ccc666393ca667e532e6636f0130106e74ca1 [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
Dan Shid37736b2016-07-06 15:10:29 -070034CONFIG = global_config.global_config
35
36_SHERIFF_JS = CONFIG.get_config_value('NOTIFICATIONS', 'sheriffs', default='')
37_LAB_SHERIFF_JS = CONFIG.get_config_value(
38 'NOTIFICATIONS', 'lab_sheriffs', default='')
39_CHROMIUM_BUILD_URL = CONFIG.get_config_value(
40 'NOTIFICATIONS', 'chromium_build_url', default='')
Alex Millerdadc2c22013-07-08 15:21:21 -070041
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -080042LAB_GOOD_STATES = ('open', 'throttled')
43
Dan Shid37736b2016-07-06 15:10:29 -070044ENABLE_DRONE_IN_RESTRICTED_SUBNET = CONFIG.get_config_value(
45 'CROS', 'enable_drone_in_restricted_subnet', type=bool,
46 default=False)
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -080047
J. Richard Barnetteabbe0962013-12-10 18:15:44 -080048class TestLabException(Exception):
49 """Exception raised when the Test Lab blocks a test or suite."""
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -080050 pass
51
52
53class ParseBuildNameException(Exception):
54 """Raised when ParseBuildName() cannot parse a build name."""
55 pass
56
57
Fang Dengf08814a2015-08-03 18:12:18 +000058class Singleton(type):
59 """Enforce that only one client class is instantiated per process."""
60 _instances = {}
61
62 def __call__(cls, *args, **kwargs):
63 """Fetch the instance of a class to use for subsequent calls."""
64 if cls not in cls._instances:
65 cls._instances[cls] = super(Singleton, cls).__call__(
66 *args, **kwargs)
67 return cls._instances[cls]
68
69
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -080070def ParseBuildName(name):
71 """Format a build name, given board, type, milestone, and manifest num.
72
Simran Basib7d21162014-05-21 15:26:16 -070073 @param name: a build name, e.g. 'x86-alex-release/R20-2015.0.0' or a
74 relative build name, e.g. 'x86-alex-release/LATEST'
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -080075
76 @return board: board the manifest is for, e.g. x86-alex.
77 @return type: one of 'release', 'factory', or 'firmware'
78 @return milestone: (numeric) milestone the manifest was associated with.
Simran Basib7d21162014-05-21 15:26:16 -070079 Will be None for relative build names.
80 @return manifest: manifest number, e.g. '2015.0.0'.
81 Will be None for relative build names.
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -080082
83 """
Simran Basif8f648e2014-09-09 11:40:03 -070084 match = re.match(r'(trybot-)?(?P<board>[\w-]+)-(?P<type>\w+)/'
85 r'(R(?P<milestone>\d+)-(?P<manifest>[\d.ab-]+)|LATEST)',
86 name)
87 if match and len(match.groups()) >= 5:
Simran Basib7d21162014-05-21 15:26:16 -070088 return (match.group('board'), match.group('type'),
89 match.group('milestone'), match.group('manifest'))
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -080090 raise ParseBuildNameException('%s is a malformed build name.' % name)
91
Alex Millerdadc2c22013-07-08 15:21:21 -070092
Dan Shi3d7a0e12015-10-12 11:55:45 -070093def get_labels_from_afe(hostname, label_prefix, afe):
94 """Retrieve a host's specific labels from the AFE.
95
96 Looks for the host labels that have the form <label_prefix>:<value>
97 and returns the "<value>" part of the label. None is returned
98 if there is not a label matching the pattern
99
100 @param hostname: hostname of given DUT.
101 @param label_prefix: prefix of label to be matched, e.g., |board:|
102 @param afe: afe instance.
103
104 @returns A list of labels that match the prefix or 'None'
105
106 """
107 labels = afe.get_labels(name__startswith=label_prefix,
108 host__hostname__in=[hostname])
109 if labels:
110 return [l.name.split(label_prefix, 1)[1] for l in labels]
111
112
Dan Shia1ecd5c2013-06-06 11:21:31 -0700113def get_label_from_afe(hostname, label_prefix, afe):
114 """Retrieve a host's specific label from the AFE.
115
116 Looks for a host label that has the form <label_prefix>:<value>
117 and returns the "<value>" part of the label. None is returned
118 if there is not a label matching the pattern
119
120 @param hostname: hostname of given DUT.
121 @param label_prefix: prefix of label to be matched, e.g., |board:|
122 @param afe: afe instance.
123 @returns the label that matches the prefix or 'None'
124
125 """
Dan Shi3d7a0e12015-10-12 11:55:45 -0700126 labels = get_labels_from_afe(hostname, label_prefix, afe)
Dan Shia1ecd5c2013-06-06 11:21:31 -0700127 if labels and len(labels) == 1:
Dan Shi3d7a0e12015-10-12 11:55:45 -0700128 return labels[0]
Dan Shia1ecd5c2013-06-06 11:21:31 -0700129
130
131def get_board_from_afe(hostname, afe):
132 """Retrieve given host's board from its labels in the AFE.
133
134 Looks for a host label of the form "board:<board>", and
135 returns the "<board>" part of the label. `None` is returned
136 if there is not a single, unique label matching the pattern.
137
138 @param hostname: hostname of given DUT.
139 @param afe: afe instance.
140 @returns board from label, or `None`.
141
142 """
143 return get_label_from_afe(hostname, constants.BOARD_PREFIX, afe)
144
145
146def get_build_from_afe(hostname, afe):
147 """Retrieve the current build for given host from the AFE.
148
149 Looks through the host's labels in the AFE to determine its build.
150
151 @param hostname: hostname of given DUT.
152 @param afe: afe instance.
153 @returns The current build or None if it could not find it or if there
154 were multiple build labels assigned to this host.
155
156 """
Simran Basi7756a0b2016-03-16 13:10:07 -0700157 for prefix in [provision.CROS_VERSION_PREFIX,
158 provision.ANDROID_BUILD_VERSION_PREFIX]:
159 build = get_label_from_afe(hostname, prefix + ':', afe)
160 if build:
161 return build
162 return None
Dan Shia1ecd5c2013-06-06 11:21:31 -0700163
164
Fang Deng3197b392013-06-26 11:42:02 -0700165def get_sheriffs(lab_only=False):
Alex Millerdadc2c22013-07-08 15:21:21 -0700166 """
167 Polls the javascript file that holds the identity of the sheriff and
168 parses it's output to return a list of chromium sheriff email addresses.
169 The javascript file can contain the ldap of more than one sheriff, eg:
170 document.write('sheriff_one, sheriff_two').
171
Fang Deng3197b392013-06-26 11:42:02 -0700172 @param lab_only: if True, only pulls lab sheriff.
173 @return: A list of chroium.org sheriff email addresses to cc on the bug.
174 An empty list if failed to parse the javascript.
Alex Millerdadc2c22013-07-08 15:21:21 -0700175 """
176 sheriff_ids = []
Fang Deng3197b392013-06-26 11:42:02 -0700177 sheriff_js_list = _LAB_SHERIFF_JS.split(',')
178 if not lab_only:
179 sheriff_js_list.extend(_SHERIFF_JS.split(','))
180
181 for sheriff_js in sheriff_js_list:
Alex Millerdadc2c22013-07-08 15:21:21 -0700182 try:
Dan Shief31f032016-05-13 15:51:39 -0700183 url_content = utils.urlopen('%s%s'% (
Alex Millerdadc2c22013-07-08 15:21:21 -0700184 _CHROMIUM_BUILD_URL, sheriff_js)).read()
185 except (ValueError, IOError) as e:
beeps4efdf032013-09-17 11:27:14 -0700186 logging.warning('could not parse sheriff from url %s%s: %s',
187 _CHROMIUM_BUILD_URL, sheriff_js, str(e))
Paul Drewsbef578d2013-09-24 15:10:36 -0700188 except (urllib2.URLError, httplib.HTTPException) as e:
189 logging.warning('unexpected error reading from url "%s%s": %s',
190 _CHROMIUM_BUILD_URL, sheriff_js, str(e))
Alex Millerdadc2c22013-07-08 15:21:21 -0700191 else:
192 ldaps = re.search(r"document.write\('(.*)'\)", url_content)
193 if not ldaps:
beeps4efdf032013-09-17 11:27:14 -0700194 logging.warning('Could not retrieve sheriff ldaps for: %s',
195 url_content)
Alex Millerdadc2c22013-07-08 15:21:21 -0700196 continue
197 sheriff_ids += ['%s@chromium.org' % alias.replace(' ', '')
198 for alias in ldaps.group(1).split(',')]
199 return sheriff_ids
beeps46dadc92013-11-07 14:07:10 -0800200
201
202def remote_wget(source_url, dest_path, ssh_cmd):
203 """wget source_url from localhost to dest_path on remote host using ssh.
204
205 @param source_url: The complete url of the source of the package to send.
206 @param dest_path: The path on the remote host's file system where we would
207 like to store the package.
208 @param ssh_cmd: The ssh command to use in performing the remote wget.
209 """
210 wget_cmd = ("wget -O - %s | %s 'cat >%s'" %
211 (source_url, ssh_cmd, dest_path))
Dan Shief31f032016-05-13 15:51:39 -0700212 utils.run(wget_cmd)
beeps46dadc92013-11-07 14:07:10 -0800213
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800214
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800215_MAX_LAB_STATUS_ATTEMPTS = 5
216def _get_lab_status(status_url):
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800217 """Grabs the current lab status and message.
218
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800219 @returns The JSON object obtained from the given URL.
220
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800221 """
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800222 retry_waittime = 1
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800223 for _ in range(_MAX_LAB_STATUS_ATTEMPTS):
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800224 try:
225 response = urllib2.urlopen(status_url)
226 except IOError as e:
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800227 logging.debug('Error occurred when grabbing the lab status: %s.',
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800228 e)
229 time.sleep(retry_waittime)
230 continue
231 # Check for successful response code.
232 if response.getcode() == 200:
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800233 return json.load(response)
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800234 time.sleep(retry_waittime)
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800235 return None
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800236
237
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800238def _decode_lab_status(lab_status, build):
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800239 """Decode lab status, and report exceptions as needed.
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800240
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800241 Take a deserialized JSON object from the lab status page, and
242 interpret it to determine the actual lab status. Raise
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800243 exceptions as required to report when the lab is down.
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800244
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800245 @param build: build name that we want to check the status of.
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800246
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800247 @raises TestLabException Raised if a request to test for the given
248 status and build should be blocked.
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800249 """
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800250 # First check if the lab is up.
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800251 if not lab_status['general_state'] in LAB_GOOD_STATES:
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800252 raise TestLabException('Chromium OS Test Lab is closed: '
253 '%s.' % lab_status['message'])
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800254
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800255 # Check if the build we wish to use is disabled.
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800256 # Lab messages should be in the format of:
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800257 # Lab is 'status' [regex ...] (comment)
258 # If the build name matches any regex, it will be blocked.
259 build_exceptions = re.search('\[(.*)\]', lab_status['message'])
Prashanth Balasubramanianae437212014-10-27 11:17:26 -0700260 if not build_exceptions or not build:
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800261 return
262 for build_pattern in build_exceptions.group(1).split():
J. Richard Barnette7f215d32015-06-19 12:44:38 -0700263 if re.match(build_pattern, build):
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800264 raise TestLabException('Chromium OS Test Lab is closed: '
265 '%s matches %s.' % (
266 build, build_pattern))
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800267 return
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800268
269
Dan Shi94234cb2014-05-23 20:04:31 -0700270def is_in_lab():
271 """Check if current Autotest instance is in lab
272
273 @return: True if the Autotest instance is in lab.
274 """
Dan Shid37736b2016-07-06 15:10:29 -0700275 test_server_name = CONFIG.get_config_value('SERVER', 'hostname')
Dan Shi94234cb2014-05-23 20:04:31 -0700276 return test_server_name.startswith('cautotest')
277
278
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800279def check_lab_status(build):
280 """Check if the lab status allows us to schedule for a build.
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800281
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800282 Checks if the lab is down, or if testing for the requested build
283 should be blocked.
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800284
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800285 @param build: Name of the build to be scheduled for testing.
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800286
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800287 @raises TestLabException Raised if a request to test for the given
288 status and build should be blocked.
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800289
290 """
291 # Ensure we are trying to schedule on the actual lab.
Dan Shi94234cb2014-05-23 20:04:31 -0700292 if not is_in_lab():
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800293 return
294
295 # Download the lab status from its home on the web.
Dan Shid37736b2016-07-06 15:10:29 -0700296 status_url = CONFIG.get_config_value('CROS', 'lab_status_url')
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800297 json_status = _get_lab_status(status_url)
298 if json_status is None:
299 # We go ahead and say the lab is open if we can't get the status.
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700300 logging.warning('Could not get a status from %s', status_url)
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800301 return
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800302 _decode_lab_status(json_status, build)
beeps023afc62014-02-04 16:59:22 -0800303
304
305def lock_host_with_labels(afe, lock_manager, labels):
306 """Lookup and lock one host that matches the list of input labels.
307
308 @param afe: An instance of the afe class, as defined in server.frontend.
309 @param lock_manager: A lock manager capable of locking hosts, eg the
310 one defined in server.cros.host_lock_manager.
311 @param labels: A list of labels to look for on hosts.
312
313 @return: The hostname of a host matching all labels, and locked through the
314 lock_manager. The hostname will be as specified in the database the afe
315 object is associated with, i.e if it exists in afe_hosts with a .cros
316 suffix, the hostname returned will contain a .cros suffix.
317
318 @raises: error.NoEligibleHostException: If no hosts matching the list of
319 input labels are available.
320 @raises: error.TestError: If unable to lock a host matching the labels.
321 """
322 potential_hosts = afe.get_hosts(multiple_labels=labels)
323 if not potential_hosts:
324 raise error.NoEligibleHostException(
325 'No devices found with labels %s.' % labels)
326
327 # This prevents errors where a fault might seem repeatable
328 # because we lock, say, the same packet capturer for each test run.
329 random.shuffle(potential_hosts)
330 for host in potential_hosts:
331 if lock_manager.lock([host.hostname]):
332 logging.info('Locked device %s with labels %s.',
333 host.hostname, labels)
334 return host.hostname
335 else:
336 logging.info('Unable to lock device %s with labels %s.',
337 host.hostname, labels)
338
339 raise error.TestError('Could not lock a device with labels %s' % labels)
Dan Shi7e04fa82013-07-25 15:08:48 -0700340
341
342def get_test_views_from_tko(suite_job_id, tko):
343 """Get test name and result for given suite job ID.
344
345 @param suite_job_id: ID of suite job.
346 @param tko: an instance of TKO as defined in server/frontend.py.
347 @return: A dictionary of test status keyed by test name, e.g.,
348 {'dummy_Fail.Error': 'ERROR', 'dummy_Fail.NAError': 'TEST_NA'}
349 @raise: Exception when there is no test view found.
350
351 """
352 views = tko.run('get_detailed_test_views', afe_job_id=suite_job_id)
353 relevant_views = filter(job_status.view_is_relevant, views)
354 if not relevant_views:
355 raise Exception('Failed to retrieve job results.')
356
357 test_views = {}
358 for view in relevant_views:
359 test_views[view['test_name']] = view['status']
360
361 return test_views
MK Ryu35d661e2014-09-25 17:44:10 -0700362
363
MK Ryuc9c0c3f2014-10-27 14:36:01 -0700364def get_data_key(prefix, suite, build, board):
365 """
366 Constructs a key string from parameters.
367
368 @param prefix: Prefix for the generating key.
369 @param suite: a suite name. e.g., bvt-cq, bvt-inline, dummy
370 @param build: The build string. This string should have a consistent
371 format eg: x86-mario-release/R26-3570.0.0. If the format of this
372 string changes such that we can't determine build_type or branch
373 we give up and use the parametes we're sure of instead (suite,
374 board). eg:
375 1. build = x86-alex-pgo-release/R26-3570.0.0
376 branch = 26
377 build_type = pgo-release
378 2. build = lumpy-paladin/R28-3993.0.0-rc5
379 branch = 28
380 build_type = paladin
381 @param board: The board that this suite ran on.
382 @return: The key string used for a dictionary.
383 """
384 try:
385 _board, build_type, branch = ParseBuildName(build)[:3]
386 except ParseBuildNameException as e:
387 logging.error(str(e))
388 branch = 'Unknown'
389 build_type = 'Unknown'
390 else:
391 embedded_str = re.search(r'x86-\w+-(.*)', _board)
392 if embedded_str:
393 build_type = embedded_str.group(1) + '-' + build_type
394
395 data_key_dict = {
396 'prefix': prefix,
397 'board': board,
398 'branch': branch,
399 'build_type': build_type,
400 'suite': suite,
401 }
402 return ('%(prefix)s.%(board)s.%(build_type)s.%(branch)s.%(suite)s'
403 % data_key_dict)
MK Ryu83184352014-12-10 14:59:40 -0800404
405
MK Ryu2d0a3642015-01-07 15:11:19 -0800406def setup_logging(logfile=None, prefix=False):
MK Ryu83184352014-12-10 14:59:40 -0800407 """Setup basic logging with all logging info stripped.
408
409 Calls to logging will only show the message. No severity is logged.
410
411 @param logfile: If specified dump output to a file as well.
MK Ryu2d0a3642015-01-07 15:11:19 -0800412 @param prefix: Flag for log prefix. Set to True to add prefix to log
413 entries to include timestamp and log level. Default is False.
MK Ryu83184352014-12-10 14:59:40 -0800414 """
415 # Remove all existing handlers. client/common_lib/logging_config adds
416 # a StreamHandler to logger when modules are imported, e.g.,
417 # autotest_lib.client.bin.utils. A new StreamHandler will be added here to
418 # log only messages, not severity.
419 logging.getLogger().handlers = []
420
MK Ryu2d0a3642015-01-07 15:11:19 -0800421 if prefix:
422 log_format = '%(asctime)s %(levelname)-5s| %(message)s'
423 else:
424 log_format = '%(message)s'
425
MK Ryu83184352014-12-10 14:59:40 -0800426 screen_handler = logging.StreamHandler()
MK Ryu2d0a3642015-01-07 15:11:19 -0800427 screen_handler.setFormatter(logging.Formatter(log_format))
MK Ryu83184352014-12-10 14:59:40 -0800428 logging.getLogger().addHandler(screen_handler)
429 logging.getLogger().setLevel(logging.INFO)
430 if logfile:
431 file_handler = logging.FileHandler(logfile)
MK Ryu2d0a3642015-01-07 15:11:19 -0800432 file_handler.setFormatter(logging.Formatter(log_format))
MK Ryu83184352014-12-10 14:59:40 -0800433 file_handler.setLevel(logging.DEBUG)
434 logging.getLogger().addHandler(file_handler)
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -0800435
436
437def is_shard():
438 """Determines if this instance is running as a shard.
439
440 Reads the global_config value shard_hostname in the section SHARD.
441
442 @return True, if shard_hostname is set, False otherwise.
443 """
Dan Shid37736b2016-07-06 15:10:29 -0700444 hostname = CONFIG.get_config_value('SHARD', 'shard_hostname', default=None)
MK Ryu0c1a37d2015-04-30 12:00:55 -0700445 return bool(hostname)
446
447
Fang Deng0cb2a3b2015-12-10 17:59:00 -0800448def get_global_afe_hostname():
449 """Read the hostname of the global AFE from the global configuration."""
Dan Shid37736b2016-07-06 15:10:29 -0700450 return CONFIG.get_config_value('SERVER', 'global_afe_hostname')
Fang Deng0cb2a3b2015-12-10 17:59:00 -0800451
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
Dan Shid37736b2016-07-06 15:10:29 -0700465 restricted_groups = CONFIG.get_config_value(
Fang Deng18699fe2015-12-04 16:40:27 -0800466 '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
Dan Shid37736b2016-07-06 15:10:29 -0700697 creds_dir = CONFIG.get_config_value('SERVER', 'creds_dir', default='')
Fang Dengf8a94e22015-12-07 13:39:13 -0800698 if not creds_dir or not os.path.exists(creds_dir):
699 creds_dir = common.autotest_dir
700 return os.path.join(creds_dir, creds_file)
Kevin Cheng3b111812015-12-15 11:52:08 -0800701
702
703def machine_is_testbed(machine):
704 """Checks if the machine is a testbed.
705
706 The signal we use to determine if the machine is a testbed
707 is if the host attributes contain more than 1 serial.
708
709 @param machine: is a list of dicts
710
711 @return: True if the machine is a testbed, False otherwise.
712 """
713 _, attributes = get_host_info_from_machine(machine)
714 if len(attributes.get('serials', '').split(',')) > 1:
715 return True
716 return False