blob: 11507ae05f070095a8c70bc4cbcb618b2c5d80d8 [file] [log] [blame]
Chris Masone44e4d6c2012-08-15 14:25:53 -07001# Copyright (c) 2012 The Chromium OS 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
5
Wade Guthrie44668f92012-10-18 09:44:49 -07006import random
Chris Sosab76e0ee2013-05-22 16:55:41 -07007import re
Wade Guthrie44668f92012-10-18 09:44:49 -07008
J. Richard Barnettee7b98bb2013-08-21 16:34:16 -07009import common
10
Chris Masone44e4d6c2012-08-15 14:25:53 -070011from autotest_lib.client.common_lib import global_config
Chris Masone44e4d6c2012-08-15 14:25:53 -070012
13
14_CONFIG = global_config.global_config
15
16
17def image_url_pattern():
Chris Sosab76e0ee2013-05-22 16:55:41 -070018 """Returns image_url_pattern from global_config."""
Chris Masone44e4d6c2012-08-15 14:25:53 -070019 return _CONFIG.get_config_value('CROS', 'image_url_pattern', type=str)
20
21
Vadim Bendeburyab14bf12012-12-28 13:51:46 -080022def firmware_url_pattern():
Chris Sosab76e0ee2013-05-22 16:55:41 -070023 """Returns firmware_url_pattern from global_config."""
Vadim Bendeburyab14bf12012-12-28 13:51:46 -080024 return _CONFIG.get_config_value('CROS', 'firmware_url_pattern', type=str)
25
26
beepse539be02013-07-31 21:57:39 -070027def factory_image_url_pattern():
28 """Returns path to factory image after it's been staged."""
29 return _CONFIG.get_config_value('CROS', 'factory_image_url_pattern',
30 type=str)
31
32
Chris Masone44e4d6c2012-08-15 14:25:53 -070033def sharding_factor():
Chris Sosab76e0ee2013-05-22 16:55:41 -070034 """Returns sharding_factor from global_config."""
Chris Masone44e4d6c2012-08-15 14:25:53 -070035 return _CONFIG.get_config_value('CROS', 'sharding_factor', type=int)
36
37
Chris Sosa66dfb372013-01-29 16:36:19 -080038def infrastructure_user():
Chris Sosab76e0ee2013-05-22 16:55:41 -070039 """Returns infrastructure_user from global_config."""
Chris Sosa66dfb372013-01-29 16:36:19 -080040 return _CONFIG.get_config_value('CROS', 'infrastructure_user', type=str)
Chris Masonee99bcf22012-08-17 15:09:49 -070041
42
Dan Shi6450e142016-03-11 11:52:20 -080043def package_url_pattern(is_launch_control_build=False):
44 """Returns package_url_pattern from global_config.
45
46 @param is_launch_control_build: True if the package url is for Launch
47 Control build. Default is False.
48 """
49 if is_launch_control_build:
50 return _CONFIG.get_config_value('ANDROID', 'package_url_pattern',
51 type=str)
52 else:
53 return _CONFIG.get_config_value('CROS', 'package_url_pattern', type=str)
Chris Masone44e4d6c2012-08-15 14:25:53 -070054
55
Alex Millerf8aafe62013-02-25 14:39:46 -080056def try_job_timeout_mins():
Chris Sosab76e0ee2013-05-22 16:55:41 -070057 """Returns try_job_timeout_mins from global_config."""
Alex Millerf8aafe62013-02-25 14:39:46 -080058 return _CONFIG.get_config_value('SCHEDULER', 'try_job_timeout_mins',
59 type=int, default=4*60)
60
61
Chris Sosaaccb5ce2012-08-30 17:29:15 -070062def get_package_url(devserver_url, build):
63 """Returns the package url from the |devserver_url| and |build|.
64
65 @param devserver_url: a string specifying the host to contact e.g.
66 http://my_host:9090.
67 @param build: the build/image string to use e.g. mario-release/R19-123.0.1.
68 @return the url where you can find the packages for the build.
69 """
Chris Masone44e4d6c2012-08-15 14:25:53 -070070 return package_url_pattern() % (devserver_url, build)
71
72
Dan Shi6450e142016-03-11 11:52:20 -080073def get_devserver_build_from_package_url(package_url,
74 is_launch_control_build=False):
Chris Sosab76e0ee2013-05-22 16:55:41 -070075 """The inverse method of get_package_url.
76
77 @param package_url: a string specifying the package url.
Dan Shi6450e142016-03-11 11:52:20 -080078 @param is_launch_control_build: True if the package url is for Launch
79 Control build. Default is False.
Chris Sosab76e0ee2013-05-22 16:55:41 -070080
81 @return tuple containing the devserver_url, build.
82 """
Dan Shi6450e142016-03-11 11:52:20 -080083 pattern = package_url_pattern(is_launch_control_build)
Chris Sosab76e0ee2013-05-22 16:55:41 -070084 re_pattern = pattern.replace('%s', '(\S+)')
joychen03eaad92013-06-26 09:55:21 -070085
86 devserver_build_tuple = re.search(re_pattern, package_url).groups()
87
88 # TODO(beeps): This is a temporary hack around the fact that all
89 # job_repo_urls in the database currently contain 'archive'. Remove
90 # when all hosts have been reimaged at least once. Ref: crbug.com/214373.
91 return (devserver_build_tuple[0],
92 devserver_build_tuple[1].replace('archive/', ''))
Chris Sosab76e0ee2013-05-22 16:55:41 -070093
94
Dan Shicf4d2032015-03-12 15:04:21 -070095def get_build_from_image(image):
96 """Get the build name from the image string.
97
98 @param image: A string of image, can be the build name or a url to the
99 build, e.g.,
100 http://devserver/update/alex-release/R27-3837.0.0
101
102 @return: Name of the build. Return None if fail to parse build name.
103 """
104 if not image.startswith('http://'):
105 return image
106 else:
107 match = re.match('.*/([^/]+/R\d+-[^/]+)', image)
108 if match:
109 return match.group(1)
110
111
Wade Guthrie44668f92012-10-18 09:44:49 -0700112def get_random_best_host(afe, host_list, require_usable_hosts=True):
113 """
114 Randomly choose the 'best' host from host_list, using fresh status.
115
116 Hit the AFE to get latest status for the listed hosts. Then apply
117 the following heuristic to pick the 'best' set:
118
119 Remove unusable hosts (not tools.is_usable()), then
120 'Ready' > 'Running, Cleaning, Verifying, etc'
121
122 If any 'Ready' hosts exist, return a random choice. If not, randomly
123 choose from the next tier. If there are none of those either, None.
124
125 @param afe: autotest front end that holds the hosts being managed.
126 @param host_list: an iterable of Host objects, per server/frontend.py
127 @param require_usable_hosts: only return hosts currently in a usable
128 state.
129 @return a Host object, or None if no appropriate host is found.
130 """
131 if not host_list:
132 return None
133 hostnames = [host.hostname for host in host_list]
134 updated_hosts = afe.get_hosts(hostnames=hostnames)
135 usable_hosts = [host for host in updated_hosts if is_usable(host)]
136 ready_hosts = [host for host in usable_hosts if host.status == 'Ready']
137 unusable_hosts = [h for h in updated_hosts if not is_usable(h)]
138 if ready_hosts:
139 return random.choice(ready_hosts)
140 if usable_hosts:
141 return random.choice(usable_hosts)
142 if not require_usable_hosts and unusable_hosts:
143 return random.choice(unusable_hosts)
144 return None
145
146
Chris Masone44e4d6c2012-08-15 14:25:53 -0700147def inject_vars(vars, control_file_in):
148 """
149 Inject the contents of |vars| into |control_file_in|.
150
151 @param vars: a dict to shoehorn into the provided control file string.
152 @param control_file_in: the contents of a control file to munge.
153 @return the modified control file string.
154 """
155 control_file = ''
156 for key, value in vars.iteritems():
157 # None gets injected as 'None' without this check; same for digits.
158 if isinstance(value, str):
Aviv Keshet7cd12312013-07-25 10:25:55 -0700159 control_file += "%s=%s\n" % (key, repr(value))
Chris Masone44e4d6c2012-08-15 14:25:53 -0700160 else:
161 control_file += "%s=%r\n" % (key, value)
Shuqian Zhaob17e33b2015-05-04 14:53:56 -0700162
163 args_dict_str = "%s=%s\n" % ('args_dict', repr(vars))
164 return control_file + args_dict_str + control_file_in
Chris Masone8906ab12012-07-23 15:37:56 -0700165
166
167def is_usable(host):
168 """
169 Given a host, determine if the host is usable right now.
170
171 @param host: Host instance (as in server/frontend.py)
172 @return True if host is alive and not incorrectly locked. Else, False.
173 """
174 return alive(host) and not incorrectly_locked(host)
175
176
177def alive(host):
178 """
179 Given a host, determine if the host is alive.
180
181 @param host: Host instance (as in server/frontend.py)
182 @return True if host is not under, or in need of, repair. Else, False.
183 """
184 return host.status not in ['Repair Failed', 'Repairing']
185
186
187def incorrectly_locked(host):
188 """
189 Given a host, determine if the host is locked by some user.
190
191 If the host is unlocked, or locked by the test infrastructure,
Chris Sosa66dfb372013-01-29 16:36:19 -0800192 this will return False. There is only one system user defined as part
193 of the test infrastructure and is listed in global_config.ini under the
194 [CROS] section in the 'infrastructure_user' field.
Chris Masone8906ab12012-07-23 15:37:56 -0700195
196 @param host: Host instance (as in server/frontend.py)
197 @return False if the host is not locked, or locked by the infra.
Chris Sosa66dfb372013-01-29 16:36:19 -0800198 True if the host is locked by the infra user.
Chris Masone8906ab12012-07-23 15:37:56 -0700199 """
Chris Sosa66dfb372013-01-29 16:36:19 -0800200 return (host.locked and host.locked_by != infrastructure_user())
J. Richard Barnettee7b98bb2013-08-21 16:34:16 -0700201
202
203def _testname_to_keyval_key(testname):
204 """Make a test name acceptable as a keyval key.
205
206 @param testname Test name that must be converted.
207 @return A string with selected bad characters replaced
208 with allowable characters.
209 """
210 # Characters for keys in autotest keyvals are restricted; in
211 # particular, '/' isn't allowed. Alas, in the case of an
212 # aborted job, the test name will be a path that includes '/'
213 # characters. We want to file bugs for aborted jobs, so we
214 # apply a transform here to avoid trouble.
J. Richard Barnetteb9c911d2013-08-23 11:24:21 -0700215 return testname.replace('/', '_')
J. Richard Barnettee7b98bb2013-08-21 16:34:16 -0700216
217
J. Richard Barnetteb9c911d2013-08-23 11:24:21 -0700218_BUG_ID_KEYVAL = '-Bug_Id'
219_BUG_COUNT_KEYVAL = '-Bug_Count'
220
221
Fang Dengdd20e452014-04-07 15:39:47 -0700222def create_bug_keyvals(job_id, testname, bug_info):
J. Richard Barnettee7b98bb2013-08-21 16:34:16 -0700223 """Create keyvals to record a bug filed against a test failure.
224
J. Richard Barnetteb9c911d2013-08-23 11:24:21 -0700225 @param testname Name of the test for which to record a bug.
226 @param bug_info Pair with the id of the bug and the count of
227 the number of times the bug has been seen.
Fang Dengdd20e452014-04-07 15:39:47 -0700228 @param job_id The afe job id of job which the test is associated to.
229 job_id will be a part of the key.
J. Richard Barnetteb9c911d2013-08-23 11:24:21 -0700230 @return Keyvals to be recorded for the given test.
J. Richard Barnettee7b98bb2013-08-21 16:34:16 -0700231 """
Fang Dengdd20e452014-04-07 15:39:47 -0700232 testname = _testname_to_keyval_key(testname)
233 keyval_base = '%s_%s' % (job_id, testname) if job_id else testname
J. Richard Barnetteb9c911d2013-08-23 11:24:21 -0700234 return {
235 keyval_base + _BUG_ID_KEYVAL: bug_info[0],
236 keyval_base + _BUG_COUNT_KEYVAL: bug_info[1]
237 }
J. Richard Barnettee7b98bb2013-08-21 16:34:16 -0700238
239
Fang Dengdd20e452014-04-07 15:39:47 -0700240def get_test_failure_bug_info(keyvals, job_id, testname):
J. Richard Barnetteb9c911d2013-08-23 11:24:21 -0700241 """Extract information about a bug filed against a test failure.
J. Richard Barnettee7b98bb2013-08-21 16:34:16 -0700242
beepsad4daf82013-09-26 10:07:33 -0700243 This method tries to extract bug_id and bug_count from the keyvals
244 of a suite. If for some reason it cannot retrieve the bug_id it will
245 return (None, None) and there will be no link to the bug filed. We will
246 instead link directly to the logs of the failed test.
247
248 If it cannot retrieve the bug_count, it will return (int(bug_id), None)
249 and this will result in a link to the bug filed, with an inline message
250 saying we weren't able to determine how many times the bug occured.
251
252 If it retrieved both the bug_id and bug_count, we return a tuple of 2
253 integers and link to the bug filed, as well as mention how many times
254 the bug has occured in the buildbot stages.
255
J. Richard Barnettee7b98bb2013-08-21 16:34:16 -0700256 @param keyvals Keyvals associated with a suite job.
Fang Dengdd20e452014-04-07 15:39:47 -0700257 @param job_id The afe job id of the job that runs the test.
J. Richard Barnettee7b98bb2013-08-21 16:34:16 -0700258 @param testname Name of a test from the suite.
J. Richard Barnetteb9c911d2013-08-23 11:24:21 -0700259 @return None if there is no bug info, or a pair with the
260 id of the bug, and the count of the number of
261 times the bug has been seen.
J. Richard Barnettee7b98bb2013-08-21 16:34:16 -0700262 """
Fang Dengdd20e452014-04-07 15:39:47 -0700263 testname = _testname_to_keyval_key(testname)
264 keyval_base = '%s_%s' % (job_id, testname) if job_id else testname
J. Richard Barnetteb9c911d2013-08-23 11:24:21 -0700265 bug_id = keyvals.get(keyval_base + _BUG_ID_KEYVAL)
beepsad4daf82013-09-26 10:07:33 -0700266 if not bug_id:
267 return None, None
268 bug_id = int(bug_id)
269 bug_count = keyvals.get(keyval_base + _BUG_COUNT_KEYVAL)
270 bug_count = int(bug_count) if bug_count else None
271 return bug_id, bug_count
Dan Shi605f7642013-11-04 16:32:54 -0800272
273
274def create_job_name(build, suite, test_name):
275 """Create the name of a test job based on given build, suite, and test_name.
276
277 @param build: name of the build, e.g., lumpy-release/R31-1234.0.0.
278 @param suite: name of the suite, e.g., bvt.
279 @param test_name: name of the test, e.g., dummy_Pass.
280 @return: the test job's name, e.g.,
281 lumpy-release/R31-1234.0.0/bvt/dummy_Pass.
282 """
283 return '/'.join([build, suite, test_name])
284
285
286def get_test_name(build, suite, test_job_name):
287 """Get the test name from test job name.
288
289 Name of test job may contain information like build and suite. This method
290 strips these information and return only the test name.
291
292 @param build: name of the build, e.g., lumpy-release/R31-1234.0.0.
293 @param suite: name of the suite, e.g., bvt.
294 @param test_job_name: name of the test job, e.g.,
295 lumpy-release/R31-1234.0.0/bvt/dummy_Pass_SERVER_JOB.
296 @return: the test name, e.g., dummy_Pass_SERVER_JOB.
297 """
Dan Shi70647ca2015-07-16 22:52:35 -0700298 # Do not change this naming convention without updating
299 # site_utils.parse_job_name.
Fang Dengdd20e452014-04-07 15:39:47 -0700300 return test_job_name.replace('%s/%s/' % (build, suite), '')