blob: 2d789af4906d3bf0c637f3153ec06622ab918bf2 [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
J. Richard Barnettee7b98bb2013-08-21 16:34:16 -070012from autotest_lib.server.cros.dynamic_suite import constants
Chris Masone44e4d6c2012-08-15 14:25:53 -070013
14
15_CONFIG = global_config.global_config
16
17
18def image_url_pattern():
Chris Sosab76e0ee2013-05-22 16:55:41 -070019 """Returns image_url_pattern from global_config."""
Chris Masone44e4d6c2012-08-15 14:25:53 -070020 return _CONFIG.get_config_value('CROS', 'image_url_pattern', type=str)
21
22
Vadim Bendeburyab14bf12012-12-28 13:51:46 -080023def firmware_url_pattern():
Chris Sosab76e0ee2013-05-22 16:55:41 -070024 """Returns firmware_url_pattern from global_config."""
Vadim Bendeburyab14bf12012-12-28 13:51:46 -080025 return _CONFIG.get_config_value('CROS', 'firmware_url_pattern', type=str)
26
27
Chris Masone44e4d6c2012-08-15 14:25:53 -070028def sharding_factor():
Chris Sosab76e0ee2013-05-22 16:55:41 -070029 """Returns sharding_factor from global_config."""
Chris Masone44e4d6c2012-08-15 14:25:53 -070030 return _CONFIG.get_config_value('CROS', 'sharding_factor', type=int)
31
32
Chris Sosa66dfb372013-01-29 16:36:19 -080033def infrastructure_user():
Chris Sosab76e0ee2013-05-22 16:55:41 -070034 """Returns infrastructure_user from global_config."""
Chris Sosa66dfb372013-01-29 16:36:19 -080035 return _CONFIG.get_config_value('CROS', 'infrastructure_user', type=str)
Chris Masonee99bcf22012-08-17 15:09:49 -070036
37
Chris Masone44e4d6c2012-08-15 14:25:53 -070038def package_url_pattern():
Chris Sosab76e0ee2013-05-22 16:55:41 -070039 """Returns package_url_pattern from global_config."""
Chris Masone44e4d6c2012-08-15 14:25:53 -070040 return _CONFIG.get_config_value('CROS', 'package_url_pattern', type=str)
41
42
Alex Millerf8aafe62013-02-25 14:39:46 -080043def try_job_timeout_mins():
Chris Sosab76e0ee2013-05-22 16:55:41 -070044 """Returns try_job_timeout_mins from global_config."""
Alex Millerf8aafe62013-02-25 14:39:46 -080045 return _CONFIG.get_config_value('SCHEDULER', 'try_job_timeout_mins',
46 type=int, default=4*60)
47
48
Chris Sosaaccb5ce2012-08-30 17:29:15 -070049def get_package_url(devserver_url, build):
50 """Returns the package url from the |devserver_url| and |build|.
51
52 @param devserver_url: a string specifying the host to contact e.g.
53 http://my_host:9090.
54 @param build: the build/image string to use e.g. mario-release/R19-123.0.1.
55 @return the url where you can find the packages for the build.
56 """
Chris Masone44e4d6c2012-08-15 14:25:53 -070057 return package_url_pattern() % (devserver_url, build)
58
59
Chris Sosab76e0ee2013-05-22 16:55:41 -070060def get_devserver_build_from_package_url(package_url):
61 """The inverse method of get_package_url.
62
63 @param package_url: a string specifying the package url.
64
65 @return tuple containing the devserver_url, build.
66 """
67 pattern = package_url_pattern()
68 re_pattern = pattern.replace('%s', '(\S+)')
joychen03eaad92013-06-26 09:55:21 -070069
70 devserver_build_tuple = re.search(re_pattern, package_url).groups()
71
72 # TODO(beeps): This is a temporary hack around the fact that all
73 # job_repo_urls in the database currently contain 'archive'. Remove
74 # when all hosts have been reimaged at least once. Ref: crbug.com/214373.
75 return (devserver_build_tuple[0],
76 devserver_build_tuple[1].replace('archive/', ''))
Chris Sosab76e0ee2013-05-22 16:55:41 -070077
78
Wade Guthrie44668f92012-10-18 09:44:49 -070079def get_random_best_host(afe, host_list, require_usable_hosts=True):
80 """
81 Randomly choose the 'best' host from host_list, using fresh status.
82
83 Hit the AFE to get latest status for the listed hosts. Then apply
84 the following heuristic to pick the 'best' set:
85
86 Remove unusable hosts (not tools.is_usable()), then
87 'Ready' > 'Running, Cleaning, Verifying, etc'
88
89 If any 'Ready' hosts exist, return a random choice. If not, randomly
90 choose from the next tier. If there are none of those either, None.
91
92 @param afe: autotest front end that holds the hosts being managed.
93 @param host_list: an iterable of Host objects, per server/frontend.py
94 @param require_usable_hosts: only return hosts currently in a usable
95 state.
96 @return a Host object, or None if no appropriate host is found.
97 """
98 if not host_list:
99 return None
100 hostnames = [host.hostname for host in host_list]
101 updated_hosts = afe.get_hosts(hostnames=hostnames)
102 usable_hosts = [host for host in updated_hosts if is_usable(host)]
103 ready_hosts = [host for host in usable_hosts if host.status == 'Ready']
104 unusable_hosts = [h for h in updated_hosts if not is_usable(h)]
105 if ready_hosts:
106 return random.choice(ready_hosts)
107 if usable_hosts:
108 return random.choice(usable_hosts)
109 if not require_usable_hosts and unusable_hosts:
110 return random.choice(unusable_hosts)
111 return None
112
113
Chris Masone44e4d6c2012-08-15 14:25:53 -0700114def inject_vars(vars, control_file_in):
115 """
116 Inject the contents of |vars| into |control_file_in|.
117
118 @param vars: a dict to shoehorn into the provided control file string.
119 @param control_file_in: the contents of a control file to munge.
120 @return the modified control file string.
121 """
122 control_file = ''
123 for key, value in vars.iteritems():
124 # None gets injected as 'None' without this check; same for digits.
125 if isinstance(value, str):
126 control_file += "%s='%s'\n" % (key, value)
127 else:
128 control_file += "%s=%r\n" % (key, value)
129 return control_file + control_file_in
Chris Masone8906ab12012-07-23 15:37:56 -0700130
131
132def is_usable(host):
133 """
134 Given a host, determine if the host is usable right now.
135
136 @param host: Host instance (as in server/frontend.py)
137 @return True if host is alive and not incorrectly locked. Else, False.
138 """
139 return alive(host) and not incorrectly_locked(host)
140
141
142def alive(host):
143 """
144 Given a host, determine if the host is alive.
145
146 @param host: Host instance (as in server/frontend.py)
147 @return True if host is not under, or in need of, repair. Else, False.
148 """
149 return host.status not in ['Repair Failed', 'Repairing']
150
151
152def incorrectly_locked(host):
153 """
154 Given a host, determine if the host is locked by some user.
155
156 If the host is unlocked, or locked by the test infrastructure,
Chris Sosa66dfb372013-01-29 16:36:19 -0800157 this will return False. There is only one system user defined as part
158 of the test infrastructure and is listed in global_config.ini under the
159 [CROS] section in the 'infrastructure_user' field.
Chris Masone8906ab12012-07-23 15:37:56 -0700160
161 @param host: Host instance (as in server/frontend.py)
162 @return False if the host is not locked, or locked by the infra.
Chris Sosa66dfb372013-01-29 16:36:19 -0800163 True if the host is locked by the infra user.
Chris Masone8906ab12012-07-23 15:37:56 -0700164 """
Chris Sosa66dfb372013-01-29 16:36:19 -0800165 return (host.locked and host.locked_by != infrastructure_user())
J. Richard Barnettee7b98bb2013-08-21 16:34:16 -0700166
167
168def _testname_to_keyval_key(testname):
169 """Make a test name acceptable as a keyval key.
170
171 @param testname Test name that must be converted.
172 @return A string with selected bad characters replaced
173 with allowable characters.
174 """
175 # Characters for keys in autotest keyvals are restricted; in
176 # particular, '/' isn't allowed. Alas, in the case of an
177 # aborted job, the test name will be a path that includes '/'
178 # characters. We want to file bugs for aborted jobs, so we
179 # apply a transform here to avoid trouble.
180 return testname.replace('/', '_') + constants.BUG_KEYVAL
181
182
183def create_bug_keyvals(testname, bug_id):
184 """Create keyvals to record a bug filed against a test failure.
185
186 @param testname Name of the test for which to record a bug.
187 @param bug_id Id of the bug to be recorded for the test.
188 @return Keyvals to be recorded for the given test.
189 """
190 return {_testname_to_keyval_key(testname): bug_id}
191
192
193def get_test_failure_bug_id(keyvals, testname):
194 """Extract the id for a bug filed against a test failure.
195
196 @param keyvals Keyvals associated with a suite job.
197 @param testname Name of a test from the suite.
198 @return Id of the bug file against the test's failure.
199 """
200 return keyvals.get(_testname_to_keyval_key(testname))