blob: 5880c674f2e8a4db38a26f81149e950953757485 [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 Shi3d7a0e12015-10-12 11:55:45 -070087def get_labels_from_afe(hostname, label_prefix, afe):
88 """Retrieve a host's specific labels from the AFE.
89
90 Looks for the host labels that have 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
98 @returns A list of labels that match the prefix or 'None'
99
100 """
101 labels = afe.get_labels(name__startswith=label_prefix,
102 host__hostname__in=[hostname])
103 if labels:
104 return [l.name.split(label_prefix, 1)[1] for l in labels]
105
106
Dan Shia1ecd5c2013-06-06 11:21:31 -0700107def get_label_from_afe(hostname, label_prefix, afe):
108 """Retrieve a host's specific label from the AFE.
109
110 Looks for a host label that has the form <label_prefix>:<value>
111 and returns the "<value>" part of the label. None is returned
112 if there is not a label matching the pattern
113
114 @param hostname: hostname of given DUT.
115 @param label_prefix: prefix of label to be matched, e.g., |board:|
116 @param afe: afe instance.
117 @returns the label that matches the prefix or 'None'
118
119 """
Dan Shi3d7a0e12015-10-12 11:55:45 -0700120 labels = get_labels_from_afe(hostname, label_prefix, afe)
Dan Shia1ecd5c2013-06-06 11:21:31 -0700121 if labels and len(labels) == 1:
Dan Shi3d7a0e12015-10-12 11:55:45 -0700122 return labels[0]
Dan Shia1ecd5c2013-06-06 11:21:31 -0700123
124
125def get_board_from_afe(hostname, afe):
126 """Retrieve given host's board from its labels in the AFE.
127
128 Looks for a host label of the form "board:<board>", and
129 returns the "<board>" part of the label. `None` is returned
130 if there is not a single, unique label matching the pattern.
131
132 @param hostname: hostname of given DUT.
133 @param afe: afe instance.
134 @returns board from label, or `None`.
135
136 """
137 return get_label_from_afe(hostname, constants.BOARD_PREFIX, afe)
138
139
140def get_build_from_afe(hostname, afe):
141 """Retrieve the current build for given host from the AFE.
142
143 Looks through the host's labels in the AFE to determine its build.
144
145 @param hostname: hostname of given DUT.
146 @param afe: afe instance.
147 @returns The current build or None if it could not find it or if there
148 were multiple build labels assigned to this host.
149
150 """
151 return get_label_from_afe(hostname, constants.VERSION_PREFIX, afe)
152
153
Fang Deng3197b392013-06-26 11:42:02 -0700154def get_sheriffs(lab_only=False):
Alex Millerdadc2c22013-07-08 15:21:21 -0700155 """
156 Polls the javascript file that holds the identity of the sheriff and
157 parses it's output to return a list of chromium sheriff email addresses.
158 The javascript file can contain the ldap of more than one sheriff, eg:
159 document.write('sheriff_one, sheriff_two').
160
Fang Deng3197b392013-06-26 11:42:02 -0700161 @param lab_only: if True, only pulls lab sheriff.
162 @return: A list of chroium.org sheriff email addresses to cc on the bug.
163 An empty list if failed to parse the javascript.
Alex Millerdadc2c22013-07-08 15:21:21 -0700164 """
165 sheriff_ids = []
Fang Deng3197b392013-06-26 11:42:02 -0700166 sheriff_js_list = _LAB_SHERIFF_JS.split(',')
167 if not lab_only:
168 sheriff_js_list.extend(_SHERIFF_JS.split(','))
169
170 for sheriff_js in sheriff_js_list:
Alex Millerdadc2c22013-07-08 15:21:21 -0700171 try:
172 url_content = base_utils.urlopen('%s%s'% (
173 _CHROMIUM_BUILD_URL, sheriff_js)).read()
174 except (ValueError, IOError) as e:
beeps4efdf032013-09-17 11:27:14 -0700175 logging.warning('could not parse sheriff from url %s%s: %s',
176 _CHROMIUM_BUILD_URL, sheriff_js, str(e))
Paul Drewsbef578d2013-09-24 15:10:36 -0700177 except (urllib2.URLError, httplib.HTTPException) as e:
178 logging.warning('unexpected error reading from url "%s%s": %s',
179 _CHROMIUM_BUILD_URL, sheriff_js, str(e))
Alex Millerdadc2c22013-07-08 15:21:21 -0700180 else:
181 ldaps = re.search(r"document.write\('(.*)'\)", url_content)
182 if not ldaps:
beeps4efdf032013-09-17 11:27:14 -0700183 logging.warning('Could not retrieve sheriff ldaps for: %s',
184 url_content)
Alex Millerdadc2c22013-07-08 15:21:21 -0700185 continue
186 sheriff_ids += ['%s@chromium.org' % alias.replace(' ', '')
187 for alias in ldaps.group(1).split(',')]
188 return sheriff_ids
beeps46dadc92013-11-07 14:07:10 -0800189
190
191def remote_wget(source_url, dest_path, ssh_cmd):
192 """wget source_url from localhost to dest_path on remote host using ssh.
193
194 @param source_url: The complete url of the source of the package to send.
195 @param dest_path: The path on the remote host's file system where we would
196 like to store the package.
197 @param ssh_cmd: The ssh command to use in performing the remote wget.
198 """
199 wget_cmd = ("wget -O - %s | %s 'cat >%s'" %
200 (source_url, ssh_cmd, dest_path))
201 base_utils.run(wget_cmd)
202
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800203
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800204_MAX_LAB_STATUS_ATTEMPTS = 5
205def _get_lab_status(status_url):
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800206 """Grabs the current lab status and message.
207
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800208 @returns The JSON object obtained from the given URL.
209
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800210 """
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800211 retry_waittime = 1
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800212 for _ in range(_MAX_LAB_STATUS_ATTEMPTS):
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800213 try:
214 response = urllib2.urlopen(status_url)
215 except IOError as e:
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800216 logging.debug('Error occurred when grabbing the lab status: %s.',
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800217 e)
218 time.sleep(retry_waittime)
219 continue
220 # Check for successful response code.
221 if response.getcode() == 200:
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800222 return json.load(response)
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800223 time.sleep(retry_waittime)
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800224 return None
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800225
226
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800227def _decode_lab_status(lab_status, build):
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800228 """Decode lab status, and report exceptions as needed.
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800229
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800230 Take a deserialized JSON object from the lab status page, and
231 interpret it to determine the actual lab status. Raise
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800232 exceptions as required to report when the lab is down.
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800233
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800234 @param build: build name that we want to check the status of.
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800235
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800236 @raises TestLabException Raised if a request to test for the given
237 status and build should be blocked.
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800238 """
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800239 # First check if the lab is up.
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800240 if not lab_status['general_state'] in LAB_GOOD_STATES:
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800241 raise TestLabException('Chromium OS Test Lab is closed: '
242 '%s.' % lab_status['message'])
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800243
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800244 # Check if the build we wish to use is disabled.
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800245 # Lab messages should be in the format of:
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800246 # Lab is 'status' [regex ...] (comment)
247 # If the build name matches any regex, it will be blocked.
248 build_exceptions = re.search('\[(.*)\]', lab_status['message'])
Prashanth Balasubramanianae437212014-10-27 11:17:26 -0700249 if not build_exceptions or not build:
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800250 return
251 for build_pattern in build_exceptions.group(1).split():
J. Richard Barnette7f215d32015-06-19 12:44:38 -0700252 if re.match(build_pattern, build):
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800253 raise TestLabException('Chromium OS Test Lab is closed: '
254 '%s matches %s.' % (
255 build, build_pattern))
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800256 return
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800257
258
Dan Shi94234cb2014-05-23 20:04:31 -0700259def is_in_lab():
260 """Check if current Autotest instance is in lab
261
262 @return: True if the Autotest instance is in lab.
263 """
264 test_server_name = global_config.global_config.get_config_value(
265 'SERVER', 'hostname')
266 return test_server_name.startswith('cautotest')
267
268
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800269def check_lab_status(build):
270 """Check if the lab status allows us to schedule for a build.
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800271
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800272 Checks if the lab is down, or if testing for the requested build
273 should be blocked.
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800274
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800275 @param build: Name of the build to be scheduled for testing.
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800276
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800277 @raises TestLabException Raised if a request to test for the given
278 status and build should be blocked.
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800279
280 """
281 # Ensure we are trying to schedule on the actual lab.
Dan Shi94234cb2014-05-23 20:04:31 -0700282 if not is_in_lab():
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800283 return
284
285 # Download the lab status from its home on the web.
286 status_url = global_config.global_config.get_config_value(
287 'CROS', 'lab_status_url')
288 json_status = _get_lab_status(status_url)
289 if json_status is None:
290 # We go ahead and say the lab is open if we can't get the status.
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700291 logging.warning('Could not get a status from %s', status_url)
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800292 return
J. Richard Barnetteabbe0962013-12-10 18:15:44 -0800293 _decode_lab_status(json_status, build)
beeps023afc62014-02-04 16:59:22 -0800294
295
296def lock_host_with_labels(afe, lock_manager, labels):
297 """Lookup and lock one host that matches the list of input labels.
298
299 @param afe: An instance of the afe class, as defined in server.frontend.
300 @param lock_manager: A lock manager capable of locking hosts, eg the
301 one defined in server.cros.host_lock_manager.
302 @param labels: A list of labels to look for on hosts.
303
304 @return: The hostname of a host matching all labels, and locked through the
305 lock_manager. The hostname will be as specified in the database the afe
306 object is associated with, i.e if it exists in afe_hosts with a .cros
307 suffix, the hostname returned will contain a .cros suffix.
308
309 @raises: error.NoEligibleHostException: If no hosts matching the list of
310 input labels are available.
311 @raises: error.TestError: If unable to lock a host matching the labels.
312 """
313 potential_hosts = afe.get_hosts(multiple_labels=labels)
314 if not potential_hosts:
315 raise error.NoEligibleHostException(
316 'No devices found with labels %s.' % labels)
317
318 # This prevents errors where a fault might seem repeatable
319 # because we lock, say, the same packet capturer for each test run.
320 random.shuffle(potential_hosts)
321 for host in potential_hosts:
322 if lock_manager.lock([host.hostname]):
323 logging.info('Locked device %s with labels %s.',
324 host.hostname, labels)
325 return host.hostname
326 else:
327 logging.info('Unable to lock device %s with labels %s.',
328 host.hostname, labels)
329
330 raise error.TestError('Could not lock a device with labels %s' % labels)
Dan Shi7e04fa82013-07-25 15:08:48 -0700331
332
333def get_test_views_from_tko(suite_job_id, tko):
334 """Get test name and result for given suite job ID.
335
336 @param suite_job_id: ID of suite job.
337 @param tko: an instance of TKO as defined in server/frontend.py.
338 @return: A dictionary of test status keyed by test name, e.g.,
339 {'dummy_Fail.Error': 'ERROR', 'dummy_Fail.NAError': 'TEST_NA'}
340 @raise: Exception when there is no test view found.
341
342 """
343 views = tko.run('get_detailed_test_views', afe_job_id=suite_job_id)
344 relevant_views = filter(job_status.view_is_relevant, views)
345 if not relevant_views:
346 raise Exception('Failed to retrieve job results.')
347
348 test_views = {}
349 for view in relevant_views:
350 test_views[view['test_name']] = view['status']
351
352 return test_views
MK Ryu35d661e2014-09-25 17:44:10 -0700353
354
355def parse_simple_config(config_file):
356 """Get paths by parsing a simple config file.
357
358 Each line of the config file is a path for a file or directory.
359 Ignore an empty line and a line starting with a hash character ('#').
360 One example of this kind of simple config file is
361 client/common_lib/logs_to_collect.
362
363 @param config_file: Config file path
364 @return: A list of directory strings
365 """
366 dirs = []
367 for l in open(config_file):
368 l = l.strip()
369 if l and not l.startswith('#'):
370 dirs.append(l)
371 return dirs
372
373
374def concat_path_except_last(base, sub):
375 """Concatenate two paths but exclude last entry.
376
377 Take two paths as parameters and return a path string in which
378 the second path becomes under the first path.
379 In addition, remove the last path entry from the concatenated path.
380 This works even when two paths are absolute paths.
381
382 e.g., /usr/local/autotest/results/ + /var/log/ =
383 /usr/local/autotest/results/var
384
385 e.g., /usr/local/autotest/results/ + /var/log/syslog =
386 /usr/local/autotest/results/var/log
387
388 @param base: Beginning path
389 @param sub: The path that is concatenated to base
390 @return: Concatenated path string
391 """
392 dirname = os.path.dirname(sub.rstrip('/'))
393 return os.path.join(base, dirname.strip('/'))
MK Ryuc9c0c3f2014-10-27 14:36:01 -0700394
395
396def get_data_key(prefix, suite, build, board):
397 """
398 Constructs a key string from parameters.
399
400 @param prefix: Prefix for the generating key.
401 @param suite: a suite name. e.g., bvt-cq, bvt-inline, dummy
402 @param build: The build string. This string should have a consistent
403 format eg: x86-mario-release/R26-3570.0.0. If the format of this
404 string changes such that we can't determine build_type or branch
405 we give up and use the parametes we're sure of instead (suite,
406 board). eg:
407 1. build = x86-alex-pgo-release/R26-3570.0.0
408 branch = 26
409 build_type = pgo-release
410 2. build = lumpy-paladin/R28-3993.0.0-rc5
411 branch = 28
412 build_type = paladin
413 @param board: The board that this suite ran on.
414 @return: The key string used for a dictionary.
415 """
416 try:
417 _board, build_type, branch = ParseBuildName(build)[:3]
418 except ParseBuildNameException as e:
419 logging.error(str(e))
420 branch = 'Unknown'
421 build_type = 'Unknown'
422 else:
423 embedded_str = re.search(r'x86-\w+-(.*)', _board)
424 if embedded_str:
425 build_type = embedded_str.group(1) + '-' + build_type
426
427 data_key_dict = {
428 'prefix': prefix,
429 'board': board,
430 'branch': branch,
431 'build_type': build_type,
432 'suite': suite,
433 }
434 return ('%(prefix)s.%(board)s.%(build_type)s.%(branch)s.%(suite)s'
435 % data_key_dict)
MK Ryu83184352014-12-10 14:59:40 -0800436
437
MK Ryu2d0a3642015-01-07 15:11:19 -0800438def setup_logging(logfile=None, prefix=False):
MK Ryu83184352014-12-10 14:59:40 -0800439 """Setup basic logging with all logging info stripped.
440
441 Calls to logging will only show the message. No severity is logged.
442
443 @param logfile: If specified dump output to a file as well.
MK Ryu2d0a3642015-01-07 15:11:19 -0800444 @param prefix: Flag for log prefix. Set to True to add prefix to log
445 entries to include timestamp and log level. Default is False.
MK Ryu83184352014-12-10 14:59:40 -0800446 """
447 # Remove all existing handlers. client/common_lib/logging_config adds
448 # a StreamHandler to logger when modules are imported, e.g.,
449 # autotest_lib.client.bin.utils. A new StreamHandler will be added here to
450 # log only messages, not severity.
451 logging.getLogger().handlers = []
452
MK Ryu2d0a3642015-01-07 15:11:19 -0800453 if prefix:
454 log_format = '%(asctime)s %(levelname)-5s| %(message)s'
455 else:
456 log_format = '%(message)s'
457
MK Ryu83184352014-12-10 14:59:40 -0800458 screen_handler = logging.StreamHandler()
MK Ryu2d0a3642015-01-07 15:11:19 -0800459 screen_handler.setFormatter(logging.Formatter(log_format))
MK Ryu83184352014-12-10 14:59:40 -0800460 logging.getLogger().addHandler(screen_handler)
461 logging.getLogger().setLevel(logging.INFO)
462 if logfile:
463 file_handler = logging.FileHandler(logfile)
MK Ryu2d0a3642015-01-07 15:11:19 -0800464 file_handler.setFormatter(logging.Formatter(log_format))
MK Ryu83184352014-12-10 14:59:40 -0800465 file_handler.setLevel(logging.DEBUG)
466 logging.getLogger().addHandler(file_handler)
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -0800467
468
469def is_shard():
470 """Determines if this instance is running as a shard.
471
472 Reads the global_config value shard_hostname in the section SHARD.
473
474 @return True, if shard_hostname is set, False otherwise.
475 """
476 hostname = global_config.global_config.get_config_value(
477 'SHARD', 'shard_hostname', default=None)
MK Ryu0c1a37d2015-04-30 12:00:55 -0700478 return bool(hostname)
479
480
481def get_special_task_status(is_complete, success, is_active):
482 """Get the status of a special task.
483
484 Emulate a host queue entry status for a special task
485 Although SpecialTasks are not HostQueueEntries, it is helpful to
486 the user to present similar statuses.
487
488 @param is_complete Boolean if the task is completed.
489 @param success Boolean if the task succeeded.
490 @param is_active Boolean if the task is active.
491
492 @return The status of a special task.
493 """
494 if is_complete:
495 if success:
496 return host_queue_entry_states.Status.COMPLETED
497 return host_queue_entry_states.Status.FAILED
498 if is_active:
499 return host_queue_entry_states.Status.RUNNING
500 return host_queue_entry_states.Status.QUEUED
501
502
503def get_special_task_exec_path(hostname, task_id, task_name, time_requested):
504 """Get the execution path of the SpecialTask.
505
506 This method returns different paths depending on where a
507 the task ran:
508 * Master: hosts/hostname/task_id-task_type
509 * Shard: Master_path/time_created
510 This is to work around the fact that a shard can fail independent
511 of the master, and be replaced by another shard that has the same
512 hosts. Without the time_created stamp the logs of the tasks running
513 on the second shard will clobber the logs from the first in google
514 storage, because task ids are not globally unique.
515
516 @param hostname Hostname
517 @param task_id Special task id
518 @param task_name Special task name (e.g., Verify, Repair, etc)
519 @param time_requested Special task requested time.
520
521 @return An execution path for the task.
522 """
523 results_path = 'hosts/%s/%s-%s' % (hostname, task_id, task_name.lower())
524
525 # If we do this on the master it will break backward compatibility,
526 # as there are tasks that currently don't have timestamps. If a host
527 # or job has been sent to a shard, the rpc for that host/job will
528 # be redirected to the shard, so this global_config check will happen
529 # on the shard the logs are on.
530 if not is_shard():
531 return results_path
532
533 # Generate a uid to disambiguate special task result directories
534 # in case this shard fails. The simplest uid is the job_id, however
535 # in rare cases tasks do not have jobs associated with them (eg:
536 # frontend verify), so just use the creation timestamp. The clocks
537 # between a shard and master should always be in sync. Any discrepancies
538 # will be brought to our attention in the form of job timeouts.
539 uid = time_requested.strftime('%Y%d%m%H%M%S')
540
541 # TODO: This is a hack, however it is the easiest way to achieve
542 # correctness. There is currently some debate over the future of
543 # tasks in our infrastructure and refactoring everything right
544 # now isn't worth the time.
545 return '%s/%s' % (results_path, uid)
546
547
548def get_job_tag(id, owner):
549 """Returns a string tag for a job.
550
551 @param id Job id
552 @param owner Job owner
553
554 """
555 return '%s-%s' % (id, owner)
556
557
558def get_hqe_exec_path(tag, execution_subdir):
559 """Returns a execution path to a HQE's results.
560
561 @param tag Tag string for a job associated with a HQE.
562 @param execution_subdir Execution sub-directory string of a HQE.
563
564 """
565 return os.path.join(tag, execution_subdir)
Dan Shi82997b92015-05-06 12:08:02 -0700566
567
568def is_inside_chroot():
569 """Check if the process is running inside chroot.
570
571 This is a wrapper around chromite.lib.cros_build_lib.IsInsideChroot(). The
572 method checks if cros_build_lib can be imported first.
573
574 @return: True if the process is running inside chroot or cros_build_lib
575 cannot be imported.
576
577 """
J. Richard Barnette7f215d32015-06-19 12:44:38 -0700578 return not cros_build_lib or cros_build_lib.IsInsideChroot()
Dan Shi70647ca2015-07-16 22:52:35 -0700579
580
581def parse_job_name(name):
582 """Parse job name to get information including build, board and suite etc.
583
584 Suite job created by run_suite follows the naming convention of:
585 [build]-test_suites/control.[suite]
586 For example: lumpy-release/R46-7272.0.0-test_suites/control.bvt
587 The naming convention is defined in site_rpc_interface.create_suite_job.
588
589 Test job created by suite job follows the naming convention of:
590 [build]/[suite]/[test name]
591 For example: lumpy-release/R46-7272.0.0/bvt/login_LoginSuccess
592 The naming convention is defined in
593 server/cros/dynamic_suite/tools.create_job_name
594
595 Note that pgo and chrome-perf builds will fail the method. Since lab does
596 not run test for these builds, they can be ignored.
597
598 @param name: Name of the job.
599
600 @return: A dictionary containing the test information. The keyvals include:
601 build: Name of the build, e.g., lumpy-release/R46-7272.0.0
602 build_version: The version of the build, e.g., R46-7272.0.0
603 board: Name of the board, e.g., lumpy
604 suite: Name of the test suite, e.g., bvt
605
606 """
607 info = {}
608 suite_job_regex = '([^/]*/[^/]*)-test_suites/control\.(.*)'
609 test_job_regex = '([^/]*/[^/]*)/([^/]+)/.*'
610 match = re.match(suite_job_regex, name)
611 if not match:
612 match = re.match(test_job_regex, name)
613 if match:
614 info['build'] = match.groups()[0]
615 info['suite'] = match.groups()[1]
616 info['build_version'] = info['build'].split('/')[1]
617 try:
618 info['board'], _, _, _ = ParseBuildName(info['build'])
619 except ParseBuildNameException:
620 pass
621 return info
Kevin Cheng3a4a57a2015-09-30 12:09:50 -0700622
623
624def add_label_detector(label_function_list, label_list=None, label=None):
625 """Decorator used to group functions together into the provided list.
626
627 This is a helper function to automatically add label functions that have
628 the label decorator. This is to help populate the class list of label
629 functions to be retrieved by the get_labels class method.
630
631 @param label_function_list: List of label detecting functions to add
632 decorated function to.
633 @param label_list: List of detectable labels to add detectable labels to.
634 (Default: None)
635 @param label: Label string that is detectable by this detection function
636 (Default: None)
637 """
638 def add_func(func):
639 """
640 @param func: The function to be added as a detector.
641 """
642 label_function_list.append(func)
643 if label and label_list is not None:
644 label_list.append(label)
645 return func
646 return add_func