blob: a4ca13cc4adbf3c2c9614d91637c4b63ab75b316 [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
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -08006import httplib
7import json
Alex Millerdadc2c22013-07-08 15:21:21 -07008import logging
MK Ryu35d661e2014-09-25 17:44:10 -07009import os
beeps023afc62014-02-04 16:59:22 -080010import random
Alex Millerdadc2c22013-07-08 15:21:21 -070011import re
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -080012import time
Paul Drewsbef578d2013-09-24 15:10:36 -070013import urllib2
Alex Millerdadc2c22013-07-08 15:21:21 -070014
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -080015import common
beeps023afc62014-02-04 16:59:22 -080016from autotest_lib.client.common_lib import base_utils
17from autotest_lib.client.common_lib import error
18from autotest_lib.client.common_lib import global_config
MK Ryu0c1a37d2015-04-30 12:00:55 -070019from autotest_lib.client.common_lib import host_queue_entry_states
Dan Shia1ecd5c2013-06-06 11:21:31 -070020from autotest_lib.server.cros.dynamic_suite import constants
Dan Shi7e04fa82013-07-25 15:08:48 -070021from autotest_lib.server.cros.dynamic_suite import job_status
Dan Shia1ecd5c2013-06-06 11:21:31 -070022
Dan Shi82997b92015-05-06 12:08:02 -070023try:
24 from chromite.lib import cros_build_lib
25except ImportError:
26 logging.warn('Unable to import chromite.')
27 # Init the module variable to None. Access to this module can check if it
28 # is not None before making calls.
29 cros_build_lib = None
30
Dan Shia1ecd5c2013-06-06 11:21:31 -070031
Alex Millerdadc2c22013-07-08 15:21:21 -070032_SHERIFF_JS = global_config.global_config.get_config_value(
33 'NOTIFICATIONS', 'sheriffs', default='')
Fang Deng3197b392013-06-26 11:42:02 -070034_LAB_SHERIFF_JS = global_config.global_config.get_config_value(
35 'NOTIFICATIONS', 'lab_sheriffs', default='')
Alex Millerdadc2c22013-07-08 15:21:21 -070036_CHROMIUM_BUILD_URL = global_config.global_config.get_config_value(
37 'NOTIFICATIONS', 'chromium_build_url', default='')
38
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -080039LAB_GOOD_STATES = ('open', 'throttled')
40
41
J. Richard Barnetteabbe0962013-12-10 18:15:44 -080042class TestLabException(Exception):
43 """Exception raised when the Test Lab blocks a test or suite."""
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -080044 pass
45
46
47class ParseBuildNameException(Exception):
48 """Raised when ParseBuildName() cannot parse a build name."""
49 pass
50
51
Fang Dengf08814a2015-08-03 18:12:18 +000052class Singleton(type):
53 """Enforce that only one client class is instantiated per process."""
54 _instances = {}
55
56 def __call__(cls, *args, **kwargs):
57 """Fetch the instance of a class to use for subsequent calls."""
58 if cls not in cls._instances:
59 cls._instances[cls] = super(Singleton, cls).__call__(
60 *args, **kwargs)
61 return cls._instances[cls]
62
63
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -080064def ParseBuildName(name):
65 """Format a build name, given board, type, milestone, and manifest num.
66
Simran Basib7d21162014-05-21 15:26:16 -070067 @param name: a build name, e.g. 'x86-alex-release/R20-2015.0.0' or a
68 relative build name, e.g. 'x86-alex-release/LATEST'
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -080069
70 @return board: board the manifest is for, e.g. x86-alex.
71 @return type: one of 'release', 'factory', or 'firmware'
72 @return milestone: (numeric) milestone the manifest was associated with.
Simran Basib7d21162014-05-21 15:26:16 -070073 Will be None for relative build names.
74 @return manifest: manifest number, e.g. '2015.0.0'.
75 Will be None for relative build names.
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -080076
77 """
Simran Basif8f648e2014-09-09 11:40:03 -070078 match = re.match(r'(trybot-)?(?P<board>[\w-]+)-(?P<type>\w+)/'
79 r'(R(?P<milestone>\d+)-(?P<manifest>[\d.ab-]+)|LATEST)',
80 name)
81 if match and len(match.groups()) >= 5:
Simran Basib7d21162014-05-21 15:26:16 -070082 return (match.group('board'), match.group('type'),
83 match.group('milestone'), match.group('manifest'))
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -080084 raise ParseBuildNameException('%s is a malformed build name.' % name)
85
Alex Millerdadc2c22013-07-08 15:21:21 -070086
Dan Shia1ecd5c2013-06-06 11:21:31 -070087def get_label_from_afe(hostname, label_prefix, afe):
88 """Retrieve a host's specific label from the AFE.
89
90 Looks for a host label that has the form <label_prefix>:<value>
91 and returns the "<value>" part of the label. None is returned
92 if there is not a label matching the pattern
93
94 @param hostname: hostname of given DUT.
95 @param label_prefix: prefix of label to be matched, e.g., |board:|
96 @param afe: afe instance.
97 @returns the label that matches the prefix or 'None'
98
99 """
100 labels = afe.get_labels(name__startswith=label_prefix,
101 host__hostname__in=[hostname])
102 if labels and len(labels) == 1:
103 return labels[0].name.split(label_prefix, 1)[1]
104
105
106def get_board_from_afe(hostname, afe):
107 """Retrieve given host's board from its labels in the AFE.
108
109 Looks for a host label of the form "board:<board>", and
110 returns the "<board>" part of the label. `None` is returned
111 if there is not a single, unique label matching the pattern.
112
113 @param hostname: hostname of given DUT.
114 @param afe: afe instance.
115 @returns board from label, or `None`.
116
117 """
118 return get_label_from_afe(hostname, constants.BOARD_PREFIX, afe)
119
120
121def get_build_from_afe(hostname, afe):
122 """Retrieve the current build for given host from the AFE.
123
124 Looks through the host's labels in the AFE to determine its build.
125
126 @param hostname: hostname of given DUT.
127 @param afe: afe instance.
128 @returns The current build or None if it could not find it or if there
129 were multiple build labels assigned to this host.
130
131 """
132 return get_label_from_afe(hostname, constants.VERSION_PREFIX, afe)
133
134
Fang Deng3197b392013-06-26 11:42:02 -0700135def get_sheriffs(lab_only=False):
Alex Millerdadc2c22013-07-08 15:21:21 -0700136 """
137 Polls the javascript file that holds the identity of the sheriff and
138 parses it's output to return a list of chromium sheriff email addresses.
139 The javascript file can contain the ldap of more than one sheriff, eg:
140 document.write('sheriff_one, sheriff_two').
141
Fang Deng3197b392013-06-26 11:42:02 -0700142 @param lab_only: if True, only pulls lab sheriff.
143 @return: A list of chroium.org sheriff email addresses to cc on the bug.
144 An empty list if failed to parse the javascript.
Alex Millerdadc2c22013-07-08 15:21:21 -0700145 """
146 sheriff_ids = []
Fang Deng3197b392013-06-26 11:42:02 -0700147 sheriff_js_list = _LAB_SHERIFF_JS.split(',')
148 if not lab_only:
149 sheriff_js_list.extend(_SHERIFF_JS.split(','))
150
151 for sheriff_js in sheriff_js_list:
Alex Millerdadc2c22013-07-08 15:21:21 -0700152 try:
153 url_content = base_utils.urlopen('%s%s'% (
154 _CHROMIUM_BUILD_URL, sheriff_js)).read()
155 except (ValueError, IOError) as e:
beeps4efdf032013-09-17 11:27:14 -0700156 logging.warning('could not parse sheriff from url %s%s: %s',
157 _CHROMIUM_BUILD_URL, sheriff_js, str(e))
Paul Drewsbef578d2013-09-24 15:10:36 -0700158 except (urllib2.URLError, httplib.HTTPException) as e:
159 logging.warning('unexpected error reading from url "%s%s": %s',
160 _CHROMIUM_BUILD_URL, sheriff_js, str(e))
Alex Millerdadc2c22013-07-08 15:21:21 -0700161 else:
162 ldaps = re.search(r"document.write\('(.*)'\)", url_content)
163 if not ldaps:
beeps4efdf032013-09-17 11:27:14 -0700164 logging.warning('Could not retrieve sheriff ldaps for: %s',
165 url_content)
Alex Millerdadc2c22013-07-08 15:21:21 -0700166 continue
167 sheriff_ids += ['%s@chromium.org' % alias.replace(' ', '')
168 for alias in ldaps.group(1).split(',')]
169 return sheriff_ids
beeps46dadc92013-11-07 14:07:10 -0800170
171
172def remote_wget(source_url, dest_path, ssh_cmd):
173 """wget source_url from localhost to dest_path on remote host using ssh.
174
175 @param source_url: The complete url of the source of the package to send.
176 @param dest_path: The path on the remote host's file system where we would
177 like to store the package.
178 @param ssh_cmd: The ssh command to use in performing the remote wget.
179 """
180 wget_cmd = ("wget -O - %s | %s 'cat >%s'" %
181 (source_url, ssh_cmd, dest_path))
182 base_utils.run(wget_cmd)
183
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800184
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800185_MAX_LAB_STATUS_ATTEMPTS = 5
186def _get_lab_status(status_url):
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800187 """Grabs the current lab status and message.
188
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800189 @returns The JSON object obtained from the given URL.
190
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800191 """
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800192 retry_waittime = 1
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800193 for _ in range(_MAX_LAB_STATUS_ATTEMPTS):
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800194 try:
195 response = urllib2.urlopen(status_url)
196 except IOError as e:
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800197 logging.debug('Error occurred when grabbing the lab status: %s.',
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800198 e)
199 time.sleep(retry_waittime)
200 continue
201 # Check for successful response code.
202 if response.getcode() == 200:
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800203 return json.load(response)
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800204 time.sleep(retry_waittime)
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800205 return None
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800206
207
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800208def _decode_lab_status(lab_status, build):
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800209 """Decode lab status, and report exceptions as needed.
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800210
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800211 Take a deserialized JSON object from the lab status page, and
212 interpret it to determine the actual lab status. Raise
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800213 exceptions as required to report when the lab is down.
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800214
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800215 @param build: build name that we want to check the status of.
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800216
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800217 @raises TestLabException Raised if a request to test for the given
218 status and build should be blocked.
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800219 """
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800220 # First check if the lab is up.
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800221 if not lab_status['general_state'] in LAB_GOOD_STATES:
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800222 raise TestLabException('Chromium OS Test Lab is closed: '
223 '%s.' % lab_status['message'])
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800224
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800225 # Check if the build we wish to use is disabled.
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800226 # Lab messages should be in the format of:
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800227 # Lab is 'status' [regex ...] (comment)
228 # If the build name matches any regex, it will be blocked.
229 build_exceptions = re.search('\[(.*)\]', lab_status['message'])
Prashanth Balasubramanianae437212014-10-27 11:17:26 -0700230 if not build_exceptions or not build:
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800231 return
232 for build_pattern in build_exceptions.group(1).split():
J. Richard Barnette7f215d32015-06-19 12:44:38 -0700233 if re.match(build_pattern, build):
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800234 raise TestLabException('Chromium OS Test Lab is closed: '
235 '%s matches %s.' % (
236 build, build_pattern))
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800237 return
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800238
239
Dan Shi94234cb2014-05-23 20:04:31 -0700240def is_in_lab():
241 """Check if current Autotest instance is in lab
242
243 @return: True if the Autotest instance is in lab.
244 """
245 test_server_name = global_config.global_config.get_config_value(
246 'SERVER', 'hostname')
247 return test_server_name.startswith('cautotest')
248
249
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800250def check_lab_status(build):
251 """Check if the lab status allows us to schedule for a build.
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800252
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800253 Checks if the lab is down, or if testing for the requested build
254 should be blocked.
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800255
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800256 @param build: Name of the build to be scheduled for testing.
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800257
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800258 @raises TestLabException Raised if a request to test for the given
259 status and build should be blocked.
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800260
261 """
262 # Ensure we are trying to schedule on the actual lab.
Dan Shi94234cb2014-05-23 20:04:31 -0700263 if not is_in_lab():
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800264 return
265
266 # Download the lab status from its home on the web.
267 status_url = global_config.global_config.get_config_value(
268 'CROS', 'lab_status_url')
269 json_status = _get_lab_status(status_url)
270 if json_status is None:
271 # We go ahead and say the lab is open if we can't get the status.
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700272 logging.warning('Could not get a status from %s', status_url)
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800273 return
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800274 _decode_lab_status(json_status, build)
beeps023afc62014-02-04 16:59:22 -0800275
276
277def lock_host_with_labels(afe, lock_manager, labels):
278 """Lookup and lock one host that matches the list of input labels.
279
280 @param afe: An instance of the afe class, as defined in server.frontend.
281 @param lock_manager: A lock manager capable of locking hosts, eg the
282 one defined in server.cros.host_lock_manager.
283 @param labels: A list of labels to look for on hosts.
284
285 @return: The hostname of a host matching all labels, and locked through the
286 lock_manager. The hostname will be as specified in the database the afe
287 object is associated with, i.e if it exists in afe_hosts with a .cros
288 suffix, the hostname returned will contain a .cros suffix.
289
290 @raises: error.NoEligibleHostException: If no hosts matching the list of
291 input labels are available.
292 @raises: error.TestError: If unable to lock a host matching the labels.
293 """
294 potential_hosts = afe.get_hosts(multiple_labels=labels)
295 if not potential_hosts:
296 raise error.NoEligibleHostException(
297 'No devices found with labels %s.' % labels)
298
299 # This prevents errors where a fault might seem repeatable
300 # because we lock, say, the same packet capturer for each test run.
301 random.shuffle(potential_hosts)
302 for host in potential_hosts:
303 if lock_manager.lock([host.hostname]):
304 logging.info('Locked device %s with labels %s.',
305 host.hostname, labels)
306 return host.hostname
307 else:
308 logging.info('Unable to lock device %s with labels %s.',
309 host.hostname, labels)
310
311 raise error.TestError('Could not lock a device with labels %s' % labels)
Dan Shi7e04fa82013-07-25 15:08:48 -0700312
313
314def get_test_views_from_tko(suite_job_id, tko):
315 """Get test name and result for given suite job ID.
316
317 @param suite_job_id: ID of suite job.
318 @param tko: an instance of TKO as defined in server/frontend.py.
319 @return: A dictionary of test status keyed by test name, e.g.,
320 {'dummy_Fail.Error': 'ERROR', 'dummy_Fail.NAError': 'TEST_NA'}
321 @raise: Exception when there is no test view found.
322
323 """
324 views = tko.run('get_detailed_test_views', afe_job_id=suite_job_id)
325 relevant_views = filter(job_status.view_is_relevant, views)
326 if not relevant_views:
327 raise Exception('Failed to retrieve job results.')
328
329 test_views = {}
330 for view in relevant_views:
331 test_views[view['test_name']] = view['status']
332
333 return test_views
MK Ryu35d661e2014-09-25 17:44:10 -0700334
335
336def parse_simple_config(config_file):
337 """Get paths by parsing a simple config file.
338
339 Each line of the config file is a path for a file or directory.
340 Ignore an empty line and a line starting with a hash character ('#').
341 One example of this kind of simple config file is
342 client/common_lib/logs_to_collect.
343
344 @param config_file: Config file path
345 @return: A list of directory strings
346 """
347 dirs = []
348 for l in open(config_file):
349 l = l.strip()
350 if l and not l.startswith('#'):
351 dirs.append(l)
352 return dirs
353
354
355def concat_path_except_last(base, sub):
356 """Concatenate two paths but exclude last entry.
357
358 Take two paths as parameters and return a path string in which
359 the second path becomes under the first path.
360 In addition, remove the last path entry from the concatenated path.
361 This works even when two paths are absolute paths.
362
363 e.g., /usr/local/autotest/results/ + /var/log/ =
364 /usr/local/autotest/results/var
365
366 e.g., /usr/local/autotest/results/ + /var/log/syslog =
367 /usr/local/autotest/results/var/log
368
369 @param base: Beginning path
370 @param sub: The path that is concatenated to base
371 @return: Concatenated path string
372 """
373 dirname = os.path.dirname(sub.rstrip('/'))
374 return os.path.join(base, dirname.strip('/'))
MK Ryuc9c0c3f2014-10-27 14:36:01 -0700375
376
377def get_data_key(prefix, suite, build, board):
378 """
379 Constructs a key string from parameters.
380
381 @param prefix: Prefix for the generating key.
382 @param suite: a suite name. e.g., bvt-cq, bvt-inline, dummy
383 @param build: The build string. This string should have a consistent
384 format eg: x86-mario-release/R26-3570.0.0. If the format of this
385 string changes such that we can't determine build_type or branch
386 we give up and use the parametes we're sure of instead (suite,
387 board). eg:
388 1. build = x86-alex-pgo-release/R26-3570.0.0
389 branch = 26
390 build_type = pgo-release
391 2. build = lumpy-paladin/R28-3993.0.0-rc5
392 branch = 28
393 build_type = paladin
394 @param board: The board that this suite ran on.
395 @return: The key string used for a dictionary.
396 """
397 try:
398 _board, build_type, branch = ParseBuildName(build)[:3]
399 except ParseBuildNameException as e:
400 logging.error(str(e))
401 branch = 'Unknown'
402 build_type = 'Unknown'
403 else:
404 embedded_str = re.search(r'x86-\w+-(.*)', _board)
405 if embedded_str:
406 build_type = embedded_str.group(1) + '-' + build_type
407
408 data_key_dict = {
409 'prefix': prefix,
410 'board': board,
411 'branch': branch,
412 'build_type': build_type,
413 'suite': suite,
414 }
415 return ('%(prefix)s.%(board)s.%(build_type)s.%(branch)s.%(suite)s'
416 % data_key_dict)
MK Ryu83184352014-12-10 14:59:40 -0800417
418
MK Ryu2d0a3642015-01-07 15:11:19 -0800419def setup_logging(logfile=None, prefix=False):
MK Ryu83184352014-12-10 14:59:40 -0800420 """Setup basic logging with all logging info stripped.
421
422 Calls to logging will only show the message. No severity is logged.
423
424 @param logfile: If specified dump output to a file as well.
MK Ryu2d0a3642015-01-07 15:11:19 -0800425 @param prefix: Flag for log prefix. Set to True to add prefix to log
426 entries to include timestamp and log level. Default is False.
MK Ryu83184352014-12-10 14:59:40 -0800427 """
428 # Remove all existing handlers. client/common_lib/logging_config adds
429 # a StreamHandler to logger when modules are imported, e.g.,
430 # autotest_lib.client.bin.utils. A new StreamHandler will be added here to
431 # log only messages, not severity.
432 logging.getLogger().handlers = []
433
MK Ryu2d0a3642015-01-07 15:11:19 -0800434 if prefix:
435 log_format = '%(asctime)s %(levelname)-5s| %(message)s'
436 else:
437 log_format = '%(message)s'
438
MK Ryu83184352014-12-10 14:59:40 -0800439 screen_handler = logging.StreamHandler()
MK Ryu2d0a3642015-01-07 15:11:19 -0800440 screen_handler.setFormatter(logging.Formatter(log_format))
MK Ryu83184352014-12-10 14:59:40 -0800441 logging.getLogger().addHandler(screen_handler)
442 logging.getLogger().setLevel(logging.INFO)
443 if logfile:
444 file_handler = logging.FileHandler(logfile)
MK Ryu2d0a3642015-01-07 15:11:19 -0800445 file_handler.setFormatter(logging.Formatter(log_format))
MK Ryu83184352014-12-10 14:59:40 -0800446 file_handler.setLevel(logging.DEBUG)
447 logging.getLogger().addHandler(file_handler)
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -0800448
449
450def is_shard():
451 """Determines if this instance is running as a shard.
452
453 Reads the global_config value shard_hostname in the section SHARD.
454
455 @return True, if shard_hostname is set, False otherwise.
456 """
457 hostname = global_config.global_config.get_config_value(
458 'SHARD', 'shard_hostname', default=None)
MK Ryu0c1a37d2015-04-30 12:00:55 -0700459 return bool(hostname)
460
461
462def get_special_task_status(is_complete, success, is_active):
463 """Get the status of a special task.
464
465 Emulate a host queue entry status for a special task
466 Although SpecialTasks are not HostQueueEntries, it is helpful to
467 the user to present similar statuses.
468
469 @param is_complete Boolean if the task is completed.
470 @param success Boolean if the task succeeded.
471 @param is_active Boolean if the task is active.
472
473 @return The status of a special task.
474 """
475 if is_complete:
476 if success:
477 return host_queue_entry_states.Status.COMPLETED
478 return host_queue_entry_states.Status.FAILED
479 if is_active:
480 return host_queue_entry_states.Status.RUNNING
481 return host_queue_entry_states.Status.QUEUED
482
483
484def get_special_task_exec_path(hostname, task_id, task_name, time_requested):
485 """Get the execution path of the SpecialTask.
486
487 This method returns different paths depending on where a
488 the task ran:
489 * Master: hosts/hostname/task_id-task_type
490 * Shard: Master_path/time_created
491 This is to work around the fact that a shard can fail independent
492 of the master, and be replaced by another shard that has the same
493 hosts. Without the time_created stamp the logs of the tasks running
494 on the second shard will clobber the logs from the first in google
495 storage, because task ids are not globally unique.
496
497 @param hostname Hostname
498 @param task_id Special task id
499 @param task_name Special task name (e.g., Verify, Repair, etc)
500 @param time_requested Special task requested time.
501
502 @return An execution path for the task.
503 """
504 results_path = 'hosts/%s/%s-%s' % (hostname, task_id, task_name.lower())
505
506 # If we do this on the master it will break backward compatibility,
507 # as there are tasks that currently don't have timestamps. If a host
508 # or job has been sent to a shard, the rpc for that host/job will
509 # be redirected to the shard, so this global_config check will happen
510 # on the shard the logs are on.
511 if not is_shard():
512 return results_path
513
514 # Generate a uid to disambiguate special task result directories
515 # in case this shard fails. The simplest uid is the job_id, however
516 # in rare cases tasks do not have jobs associated with them (eg:
517 # frontend verify), so just use the creation timestamp. The clocks
518 # between a shard and master should always be in sync. Any discrepancies
519 # will be brought to our attention in the form of job timeouts.
520 uid = time_requested.strftime('%Y%d%m%H%M%S')
521
522 # TODO: This is a hack, however it is the easiest way to achieve
523 # correctness. There is currently some debate over the future of
524 # tasks in our infrastructure and refactoring everything right
525 # now isn't worth the time.
526 return '%s/%s' % (results_path, uid)
527
528
529def get_job_tag(id, owner):
530 """Returns a string tag for a job.
531
532 @param id Job id
533 @param owner Job owner
534
535 """
536 return '%s-%s' % (id, owner)
537
538
539def get_hqe_exec_path(tag, execution_subdir):
540 """Returns a execution path to a HQE's results.
541
542 @param tag Tag string for a job associated with a HQE.
543 @param execution_subdir Execution sub-directory string of a HQE.
544
545 """
546 return os.path.join(tag, execution_subdir)
Dan Shi82997b92015-05-06 12:08:02 -0700547
548
549def is_inside_chroot():
550 """Check if the process is running inside chroot.
551
552 This is a wrapper around chromite.lib.cros_build_lib.IsInsideChroot(). The
553 method checks if cros_build_lib can be imported first.
554
555 @return: True if the process is running inside chroot or cros_build_lib
556 cannot be imported.
557
558 """
J. Richard Barnette7f215d32015-06-19 12:44:38 -0700559 return not cros_build_lib or cros_build_lib.IsInsideChroot()
Dan Shi70647ca2015-07-16 22:52:35 -0700560
561
562def parse_job_name(name):
563 """Parse job name to get information including build, board and suite etc.
564
565 Suite job created by run_suite follows the naming convention of:
566 [build]-test_suites/control.[suite]
567 For example: lumpy-release/R46-7272.0.0-test_suites/control.bvt
568 The naming convention is defined in site_rpc_interface.create_suite_job.
569
570 Test job created by suite job follows the naming convention of:
571 [build]/[suite]/[test name]
572 For example: lumpy-release/R46-7272.0.0/bvt/login_LoginSuccess
573 The naming convention is defined in
574 server/cros/dynamic_suite/tools.create_job_name
575
576 Note that pgo and chrome-perf builds will fail the method. Since lab does
577 not run test for these builds, they can be ignored.
578
579 @param name: Name of the job.
580
581 @return: A dictionary containing the test information. The keyvals include:
582 build: Name of the build, e.g., lumpy-release/R46-7272.0.0
583 build_version: The version of the build, e.g., R46-7272.0.0
584 board: Name of the board, e.g., lumpy
585 suite: Name of the test suite, e.g., bvt
586
587 """
588 info = {}
589 suite_job_regex = '([^/]*/[^/]*)-test_suites/control\.(.*)'
590 test_job_regex = '([^/]*/[^/]*)/([^/]+)/.*'
591 match = re.match(suite_job_regex, name)
592 if not match:
593 match = re.match(test_job_regex, name)
594 if match:
595 info['build'] = match.groups()[0]
596 info['suite'] = match.groups()[1]
597 info['build_version'] = info['build'].split('/')[1]
598 try:
599 info['board'], _, _, _ = ParseBuildName(info['build'])
600 except ParseBuildNameException:
601 pass
602 return info
Kevin Cheng3a4a57a2015-09-30 12:09:50 -0700603
604
605def add_label_detector(label_function_list, label_list=None, label=None):
606 """Decorator used to group functions together into the provided list.
607
608 This is a helper function to automatically add label functions that have
609 the label decorator. This is to help populate the class list of label
610 functions to be retrieved by the get_labels class method.
611
612 @param label_function_list: List of label detecting functions to add
613 decorated function to.
614 @param label_list: List of detectable labels to add detectable labels to.
615 (Default: None)
616 @param label: Label string that is detectable by this detection function
617 (Default: None)
618 """
619 def add_func(func):
620 """
621 @param func: The function to be added as a detector.
622 """
623 label_function_list.append(func)
624 if label and label_list is not None:
625 label_list.append(label)
626 return func
627 return add_func