blob: 7e8aaeeeec2043d7210324d8f2bb91b8c10d23d2 [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
Chris Masone44e4d6c2012-08-15 14:25:53 -070027def sharding_factor():
Chris Sosab76e0ee2013-05-22 16:55:41 -070028 """Returns sharding_factor from global_config."""
Chris Masone44e4d6c2012-08-15 14:25:53 -070029 return _CONFIG.get_config_value('CROS', 'sharding_factor', type=int)
30
31
Chris Sosa66dfb372013-01-29 16:36:19 -080032def infrastructure_user():
Chris Sosab76e0ee2013-05-22 16:55:41 -070033 """Returns infrastructure_user from global_config."""
Chris Sosa66dfb372013-01-29 16:36:19 -080034 return _CONFIG.get_config_value('CROS', 'infrastructure_user', type=str)
Chris Masonee99bcf22012-08-17 15:09:49 -070035
36
Chris Masone44e4d6c2012-08-15 14:25:53 -070037def package_url_pattern():
Chris Sosab76e0ee2013-05-22 16:55:41 -070038 """Returns package_url_pattern from global_config."""
Chris Masone44e4d6c2012-08-15 14:25:53 -070039 return _CONFIG.get_config_value('CROS', 'package_url_pattern', type=str)
40
41
Alex Millerf8aafe62013-02-25 14:39:46 -080042def try_job_timeout_mins():
Chris Sosab76e0ee2013-05-22 16:55:41 -070043 """Returns try_job_timeout_mins from global_config."""
Alex Millerf8aafe62013-02-25 14:39:46 -080044 return _CONFIG.get_config_value('SCHEDULER', 'try_job_timeout_mins',
45 type=int, default=4*60)
46
47
Chris Sosaaccb5ce2012-08-30 17:29:15 -070048def get_package_url(devserver_url, build):
49 """Returns the package url from the |devserver_url| and |build|.
50
51 @param devserver_url: a string specifying the host to contact e.g.
52 http://my_host:9090.
53 @param build: the build/image string to use e.g. mario-release/R19-123.0.1.
54 @return the url where you can find the packages for the build.
55 """
Chris Masone44e4d6c2012-08-15 14:25:53 -070056 return package_url_pattern() % (devserver_url, build)
57
58
Chris Sosab76e0ee2013-05-22 16:55:41 -070059def get_devserver_build_from_package_url(package_url):
60 """The inverse method of get_package_url.
61
62 @param package_url: a string specifying the package url.
63
64 @return tuple containing the devserver_url, build.
65 """
66 pattern = package_url_pattern()
67 re_pattern = pattern.replace('%s', '(\S+)')
joychen03eaad92013-06-26 09:55:21 -070068
69 devserver_build_tuple = re.search(re_pattern, package_url).groups()
70
71 # TODO(beeps): This is a temporary hack around the fact that all
72 # job_repo_urls in the database currently contain 'archive'. Remove
73 # when all hosts have been reimaged at least once. Ref: crbug.com/214373.
74 return (devserver_build_tuple[0],
75 devserver_build_tuple[1].replace('archive/', ''))
Chris Sosab76e0ee2013-05-22 16:55:41 -070076
77
Wade Guthrie44668f92012-10-18 09:44:49 -070078def get_random_best_host(afe, host_list, require_usable_hosts=True):
79 """
80 Randomly choose the 'best' host from host_list, using fresh status.
81
82 Hit the AFE to get latest status for the listed hosts. Then apply
83 the following heuristic to pick the 'best' set:
84
85 Remove unusable hosts (not tools.is_usable()), then
86 'Ready' > 'Running, Cleaning, Verifying, etc'
87
88 If any 'Ready' hosts exist, return a random choice. If not, randomly
89 choose from the next tier. If there are none of those either, None.
90
91 @param afe: autotest front end that holds the hosts being managed.
92 @param host_list: an iterable of Host objects, per server/frontend.py
93 @param require_usable_hosts: only return hosts currently in a usable
94 state.
95 @return a Host object, or None if no appropriate host is found.
96 """
97 if not host_list:
98 return None
99 hostnames = [host.hostname for host in host_list]
100 updated_hosts = afe.get_hosts(hostnames=hostnames)
101 usable_hosts = [host for host in updated_hosts if is_usable(host)]
102 ready_hosts = [host for host in usable_hosts if host.status == 'Ready']
103 unusable_hosts = [h for h in updated_hosts if not is_usable(h)]
104 if ready_hosts:
105 return random.choice(ready_hosts)
106 if usable_hosts:
107 return random.choice(usable_hosts)
108 if not require_usable_hosts and unusable_hosts:
109 return random.choice(unusable_hosts)
110 return None
111
112
Chris Masone44e4d6c2012-08-15 14:25:53 -0700113def inject_vars(vars, control_file_in):
114 """
115 Inject the contents of |vars| into |control_file_in|.
116
117 @param vars: a dict to shoehorn into the provided control file string.
118 @param control_file_in: the contents of a control file to munge.
119 @return the modified control file string.
120 """
121 control_file = ''
122 for key, value in vars.iteritems():
123 # None gets injected as 'None' without this check; same for digits.
124 if isinstance(value, str):
125 control_file += "%s='%s'\n" % (key, value)
126 else:
127 control_file += "%s=%r\n" % (key, value)
128 return control_file + control_file_in
Chris Masone8906ab12012-07-23 15:37:56 -0700129
130
131def is_usable(host):
132 """
133 Given a host, determine if the host is usable right now.
134
135 @param host: Host instance (as in server/frontend.py)
136 @return True if host is alive and not incorrectly locked. Else, False.
137 """
138 return alive(host) and not incorrectly_locked(host)
139
140
141def alive(host):
142 """
143 Given a host, determine if the host is alive.
144
145 @param host: Host instance (as in server/frontend.py)
146 @return True if host is not under, or in need of, repair. Else, False.
147 """
148 return host.status not in ['Repair Failed', 'Repairing']
149
150
151def incorrectly_locked(host):
152 """
153 Given a host, determine if the host is locked by some user.
154
155 If the host is unlocked, or locked by the test infrastructure,
Chris Sosa66dfb372013-01-29 16:36:19 -0800156 this will return False. There is only one system user defined as part
157 of the test infrastructure and is listed in global_config.ini under the
158 [CROS] section in the 'infrastructure_user' field.
Chris Masone8906ab12012-07-23 15:37:56 -0700159
160 @param host: Host instance (as in server/frontend.py)
161 @return False if the host is not locked, or locked by the infra.
Chris Sosa66dfb372013-01-29 16:36:19 -0800162 True if the host is locked by the infra user.
Chris Masone8906ab12012-07-23 15:37:56 -0700163 """
Chris Sosa66dfb372013-01-29 16:36:19 -0800164 return (host.locked and host.locked_by != infrastructure_user())
J. Richard Barnettee7b98bb2013-08-21 16:34:16 -0700165
166
167def _testname_to_keyval_key(testname):
168 """Make a test name acceptable as a keyval key.
169
170 @param testname Test name that must be converted.
171 @return A string with selected bad characters replaced
172 with allowable characters.
173 """
174 # Characters for keys in autotest keyvals are restricted; in
175 # particular, '/' isn't allowed. Alas, in the case of an
176 # aborted job, the test name will be a path that includes '/'
177 # characters. We want to file bugs for aborted jobs, so we
178 # apply a transform here to avoid trouble.
J. Richard Barnetteb9c911d2013-08-23 11:24:21 -0700179 return testname.replace('/', '_')
J. Richard Barnettee7b98bb2013-08-21 16:34:16 -0700180
181
J. Richard Barnetteb9c911d2013-08-23 11:24:21 -0700182_BUG_ID_KEYVAL = '-Bug_Id'
183_BUG_COUNT_KEYVAL = '-Bug_Count'
184
185
186def create_bug_keyvals(testname, bug_info):
J. Richard Barnettee7b98bb2013-08-21 16:34:16 -0700187 """Create keyvals to record a bug filed against a test failure.
188
J. Richard Barnetteb9c911d2013-08-23 11:24:21 -0700189 @param testname Name of the test for which to record a bug.
190 @param bug_info Pair with the id of the bug and the count of
191 the number of times the bug has been seen.
192 @return Keyvals to be recorded for the given test.
J. Richard Barnettee7b98bb2013-08-21 16:34:16 -0700193 """
J. Richard Barnetteb9c911d2013-08-23 11:24:21 -0700194 keyval_base = _testname_to_keyval_key(testname)
195 return {
196 keyval_base + _BUG_ID_KEYVAL: bug_info[0],
197 keyval_base + _BUG_COUNT_KEYVAL: bug_info[1]
198 }
J. Richard Barnettee7b98bb2013-08-21 16:34:16 -0700199
200
J. Richard Barnetteb9c911d2013-08-23 11:24:21 -0700201def get_test_failure_bug_info(keyvals, testname):
202 """Extract information about a bug filed against a test failure.
J. Richard Barnettee7b98bb2013-08-21 16:34:16 -0700203
204 @param keyvals Keyvals associated with a suite job.
205 @param testname Name of a test from the suite.
J. Richard Barnetteb9c911d2013-08-23 11:24:21 -0700206 @return None if there is no bug info, or a pair with the
207 id of the bug, and the count of the number of
208 times the bug has been seen.
J. Richard Barnettee7b98bb2013-08-21 16:34:16 -0700209 """
J. Richard Barnetteb9c911d2013-08-23 11:24:21 -0700210 keyval_base = _testname_to_keyval_key(testname)
211 bug_id = keyvals.get(keyval_base + _BUG_ID_KEYVAL)
212 if bug_id is not None:
213 bug_count = keyvals.get(keyval_base + _BUG_COUNT_KEYVAL)
214 return bug_id, bug_count
215 return bug_id