blob: 374af594256a264b77c52cdd2cab9ee708b80a76 [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
Chris Masone44e4d6c2012-08-15 14:25:53 -070043def package_url_pattern():
Chris Sosab76e0ee2013-05-22 16:55:41 -070044 """Returns package_url_pattern from global_config."""
Chris Masone44e4d6c2012-08-15 14:25:53 -070045 return _CONFIG.get_config_value('CROS', 'package_url_pattern', type=str)
46
47
Alex Millerf8aafe62013-02-25 14:39:46 -080048def try_job_timeout_mins():
Chris Sosab76e0ee2013-05-22 16:55:41 -070049 """Returns try_job_timeout_mins from global_config."""
Alex Millerf8aafe62013-02-25 14:39:46 -080050 return _CONFIG.get_config_value('SCHEDULER', 'try_job_timeout_mins',
51 type=int, default=4*60)
52
53
Chris Sosaaccb5ce2012-08-30 17:29:15 -070054def get_package_url(devserver_url, build):
55 """Returns the package url from the |devserver_url| and |build|.
56
57 @param devserver_url: a string specifying the host to contact e.g.
58 http://my_host:9090.
59 @param build: the build/image string to use e.g. mario-release/R19-123.0.1.
60 @return the url where you can find the packages for the build.
61 """
Chris Masone44e4d6c2012-08-15 14:25:53 -070062 return package_url_pattern() % (devserver_url, build)
63
64
Chris Sosab76e0ee2013-05-22 16:55:41 -070065def get_devserver_build_from_package_url(package_url):
66 """The inverse method of get_package_url.
67
68 @param package_url: a string specifying the package url.
69
70 @return tuple containing the devserver_url, build.
71 """
72 pattern = package_url_pattern()
73 re_pattern = pattern.replace('%s', '(\S+)')
joychen03eaad92013-06-26 09:55:21 -070074
75 devserver_build_tuple = re.search(re_pattern, package_url).groups()
76
77 # TODO(beeps): This is a temporary hack around the fact that all
78 # job_repo_urls in the database currently contain 'archive'. Remove
79 # when all hosts have been reimaged at least once. Ref: crbug.com/214373.
80 return (devserver_build_tuple[0],
81 devserver_build_tuple[1].replace('archive/', ''))
Chris Sosab76e0ee2013-05-22 16:55:41 -070082
83
Wade Guthrie44668f92012-10-18 09:44:49 -070084def get_random_best_host(afe, host_list, require_usable_hosts=True):
85 """
86 Randomly choose the 'best' host from host_list, using fresh status.
87
88 Hit the AFE to get latest status for the listed hosts. Then apply
89 the following heuristic to pick the 'best' set:
90
91 Remove unusable hosts (not tools.is_usable()), then
92 'Ready' > 'Running, Cleaning, Verifying, etc'
93
94 If any 'Ready' hosts exist, return a random choice. If not, randomly
95 choose from the next tier. If there are none of those either, None.
96
97 @param afe: autotest front end that holds the hosts being managed.
98 @param host_list: an iterable of Host objects, per server/frontend.py
99 @param require_usable_hosts: only return hosts currently in a usable
100 state.
101 @return a Host object, or None if no appropriate host is found.
102 """
103 if not host_list:
104 return None
105 hostnames = [host.hostname for host in host_list]
106 updated_hosts = afe.get_hosts(hostnames=hostnames)
107 usable_hosts = [host for host in updated_hosts if is_usable(host)]
108 ready_hosts = [host for host in usable_hosts if host.status == 'Ready']
109 unusable_hosts = [h for h in updated_hosts if not is_usable(h)]
110 if ready_hosts:
111 return random.choice(ready_hosts)
112 if usable_hosts:
113 return random.choice(usable_hosts)
114 if not require_usable_hosts and unusable_hosts:
115 return random.choice(unusable_hosts)
116 return None
117
118
Chris Masone44e4d6c2012-08-15 14:25:53 -0700119def inject_vars(vars, control_file_in):
120 """
121 Inject the contents of |vars| into |control_file_in|.
122
123 @param vars: a dict to shoehorn into the provided control file string.
124 @param control_file_in: the contents of a control file to munge.
125 @return the modified control file string.
126 """
127 control_file = ''
128 for key, value in vars.iteritems():
129 # None gets injected as 'None' without this check; same for digits.
130 if isinstance(value, str):
Aviv Keshet7cd12312013-07-25 10:25:55 -0700131 control_file += "%s=%s\n" % (key, repr(value))
Chris Masone44e4d6c2012-08-15 14:25:53 -0700132 else:
133 control_file += "%s=%r\n" % (key, value)
134 return control_file + control_file_in
Chris Masone8906ab12012-07-23 15:37:56 -0700135
136
137def is_usable(host):
138 """
139 Given a host, determine if the host is usable right now.
140
141 @param host: Host instance (as in server/frontend.py)
142 @return True if host is alive and not incorrectly locked. Else, False.
143 """
144 return alive(host) and not incorrectly_locked(host)
145
146
147def alive(host):
148 """
149 Given a host, determine if the host is alive.
150
151 @param host: Host instance (as in server/frontend.py)
152 @return True if host is not under, or in need of, repair. Else, False.
153 """
154 return host.status not in ['Repair Failed', 'Repairing']
155
156
157def incorrectly_locked(host):
158 """
159 Given a host, determine if the host is locked by some user.
160
161 If the host is unlocked, or locked by the test infrastructure,
Chris Sosa66dfb372013-01-29 16:36:19 -0800162 this will return False. There is only one system user defined as part
163 of the test infrastructure and is listed in global_config.ini under the
164 [CROS] section in the 'infrastructure_user' field.
Chris Masone8906ab12012-07-23 15:37:56 -0700165
166 @param host: Host instance (as in server/frontend.py)
167 @return False if the host is not locked, or locked by the infra.
Chris Sosa66dfb372013-01-29 16:36:19 -0800168 True if the host is locked by the infra user.
Chris Masone8906ab12012-07-23 15:37:56 -0700169 """
Chris Sosa66dfb372013-01-29 16:36:19 -0800170 return (host.locked and host.locked_by != infrastructure_user())
J. Richard Barnettee7b98bb2013-08-21 16:34:16 -0700171
172
173def _testname_to_keyval_key(testname):
174 """Make a test name acceptable as a keyval key.
175
176 @param testname Test name that must be converted.
177 @return A string with selected bad characters replaced
178 with allowable characters.
179 """
180 # Characters for keys in autotest keyvals are restricted; in
181 # particular, '/' isn't allowed. Alas, in the case of an
182 # aborted job, the test name will be a path that includes '/'
183 # characters. We want to file bugs for aborted jobs, so we
184 # apply a transform here to avoid trouble.
J. Richard Barnetteb9c911d2013-08-23 11:24:21 -0700185 return testname.replace('/', '_')
J. Richard Barnettee7b98bb2013-08-21 16:34:16 -0700186
187
J. Richard Barnetteb9c911d2013-08-23 11:24:21 -0700188_BUG_ID_KEYVAL = '-Bug_Id'
189_BUG_COUNT_KEYVAL = '-Bug_Count'
190
191
192def create_bug_keyvals(testname, bug_info):
J. Richard Barnettee7b98bb2013-08-21 16:34:16 -0700193 """Create keyvals to record a bug filed against a test failure.
194
J. Richard Barnetteb9c911d2013-08-23 11:24:21 -0700195 @param testname Name of the test for which to record a bug.
196 @param bug_info Pair with the id of the bug and the count of
197 the number of times the bug has been seen.
198 @return Keyvals to be recorded for the given test.
J. Richard Barnettee7b98bb2013-08-21 16:34:16 -0700199 """
J. Richard Barnetteb9c911d2013-08-23 11:24:21 -0700200 keyval_base = _testname_to_keyval_key(testname)
201 return {
202 keyval_base + _BUG_ID_KEYVAL: bug_info[0],
203 keyval_base + _BUG_COUNT_KEYVAL: bug_info[1]
204 }
J. Richard Barnettee7b98bb2013-08-21 16:34:16 -0700205
206
J. Richard Barnetteb9c911d2013-08-23 11:24:21 -0700207def get_test_failure_bug_info(keyvals, testname):
208 """Extract information about a bug filed against a test failure.
J. Richard Barnettee7b98bb2013-08-21 16:34:16 -0700209
beepsad4daf82013-09-26 10:07:33 -0700210 This method tries to extract bug_id and bug_count from the keyvals
211 of a suite. If for some reason it cannot retrieve the bug_id it will
212 return (None, None) and there will be no link to the bug filed. We will
213 instead link directly to the logs of the failed test.
214
215 If it cannot retrieve the bug_count, it will return (int(bug_id), None)
216 and this will result in a link to the bug filed, with an inline message
217 saying we weren't able to determine how many times the bug occured.
218
219 If it retrieved both the bug_id and bug_count, we return a tuple of 2
220 integers and link to the bug filed, as well as mention how many times
221 the bug has occured in the buildbot stages.
222
J. Richard Barnettee7b98bb2013-08-21 16:34:16 -0700223 @param keyvals Keyvals associated with a suite job.
224 @param testname Name of a test from the suite.
J. Richard Barnetteb9c911d2013-08-23 11:24:21 -0700225 @return None if there is no bug info, or a pair with the
226 id of the bug, and the count of the number of
227 times the bug has been seen.
J. Richard Barnettee7b98bb2013-08-21 16:34:16 -0700228 """
J. Richard Barnetteb9c911d2013-08-23 11:24:21 -0700229 keyval_base = _testname_to_keyval_key(testname)
230 bug_id = keyvals.get(keyval_base + _BUG_ID_KEYVAL)
beepsad4daf82013-09-26 10:07:33 -0700231 if not bug_id:
232 return None, None
233 bug_id = int(bug_id)
234 bug_count = keyvals.get(keyval_base + _BUG_COUNT_KEYVAL)
235 bug_count = int(bug_count) if bug_count else None
236 return bug_id, bug_count