blob: 1557b8f6b3092ee4bd0ad73448fe9ba3ead2166f [file] [log] [blame]
Chris Masone6a0680f2012-03-02 08:40:00 -08001# Copyright (c) 2012 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.
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -08004
Simran Basi87d7a212012-09-27 10:41:05 -07005import logging
Simran Basiaf9b8e72012-10-12 15:02:36 -07006import os
Fang Deng7c2be102012-08-27 16:20:25 -07007import re
Simran Basiaf9b8e72012-10-12 15:02:36 -07008import signal
Scott Zawalski347a0b82012-03-30 16:39:21 -04009import socket
Simran Basiaf9b8e72012-10-12 15:02:36 -070010import time
beeps60aec242013-06-26 14:47:48 -070011import urllib2
Simran Basidd129972014-09-11 14:34:49 -070012import uuid
Chris Masone6a0680f2012-03-02 08:40:00 -080013
Simran Basiaf9b8e72012-10-12 15:02:36 -070014from autotest_lib.client.common_lib import base_utils, error, global_config
beepsc4fb1472013-05-08 21:49:48 -070015from autotest_lib.client.cros import constants
Simran Basiaf9b8e72012-10-12 15:02:36 -070016
17
18# Keep checking if the pid is alive every second until the timeout (in seconds)
19CHECK_PID_IS_ALIVE_TIMEOUT = 6
20
Simran Basi22aa9fe2012-12-07 16:37:09 -080021_LOCAL_HOST_LIST = ('localhost', '127.0.0.1')
22
Simran Basidd129972014-09-11 14:34:49 -070023# Google Storage bucket URI to store results in.
24DEFAULT_OFFLOAD_GSURI = global_config.global_config.get_config_value(
25 'CROS', 'results_storage_server', default=None)
26
27# Default Moblab Ethernet Interface.
28MOBLAB_ETH = 'eth0'
Fang Deng3197b392013-06-26 11:42:02 -070029
Chris Masone6a0680f2012-03-02 08:40:00 -080030def ping(host, deadline=None, tries=None, timeout=60):
31 """Attempt to ping |host|.
32
33 Shell out to 'ping' to try to reach |host| for |timeout| seconds.
34 Returns exit code of ping.
35
36 Per 'man ping', if you specify BOTH |deadline| and |tries|, ping only
37 returns 0 if we get responses to |tries| pings within |deadline| seconds.
38
39 Specifying |deadline| or |count| alone should return 0 as long as
40 some packets receive responses.
41
beepsfda8f412013-05-02 19:08:20 -070042 @param host: the host to ping.
Chris Masone6a0680f2012-03-02 08:40:00 -080043 @param deadline: seconds within which |tries| pings must succeed.
44 @param tries: number of pings to send.
45 @param timeout: number of seconds after which to kill 'ping' command.
46 @return exit code of ping command.
47 """
48 args = [host]
49 if deadline:
50 args.append('-w%d' % deadline)
51 if tries:
52 args.append('-c%d' % tries)
53 return base_utils.run('ping', args=args,
54 ignore_status=True, timeout=timeout,
Scott Zawalskiae843542012-03-20 09:51:29 -040055 stdout_tee=base_utils.TEE_TO_LOGS,
56 stderr_tee=base_utils.TEE_TO_LOGS).exit_status
Scott Zawalski347a0b82012-03-30 16:39:21 -040057
58
59def host_is_in_lab_zone(hostname):
Hung-ying Tyancbdd1982014-09-03 16:54:08 +080060 """Check if the host is in the CLIENT.dns_zone.
Scott Zawalski347a0b82012-03-30 16:39:21 -040061
62 @param hostname: The hostname to check.
63 @returns True if hostname.dns_zone resolves, otherwise False.
64 """
65 host_parts = hostname.split('.')
Hung-ying Tyancbdd1982014-09-03 16:54:08 +080066 dns_zone = global_config.global_config.get_config_value('CLIENT', 'dns_zone',
Scott Zawalski347a0b82012-03-30 16:39:21 -040067 default=None)
68 fqdn = '%s.%s' % (host_parts[0], dns_zone)
69 try:
70 socket.gethostbyname(fqdn)
71 return True
72 except socket.gaierror:
Ilja H. Friedel8753d832014-10-07 22:30:06 -070073 return False
Fang Deng7c2be102012-08-27 16:20:25 -070074
75
beepsc4fb1472013-05-08 21:49:48 -070076def get_chrome_version(job_views):
77 """
78 Retrieves the version of the chrome binary associated with a job.
79
80 When a test runs we query the chrome binary for it's version and drop
81 that value into a client keyval. To retrieve the chrome version we get all
82 the views associated with a test from the db, including those of the
83 server and client jobs, and parse the version out of the first test view
84 that has it. If we never ran a single test in the suite the job_views
85 dictionary will not contain a chrome version.
86
87 This method cannot retrieve the chrome version from a dictionary that
88 does not conform to the structure of an autotest tko view.
89
90 @param job_views: a list of a job's result views, as returned by
91 the get_detailed_test_views method in rpc_interface.
92 @return: The chrome version string, or None if one can't be found.
93 """
94
95 # Aborted jobs have no views.
96 if not job_views:
97 return None
98
99 for view in job_views:
100 if (view.get('attributes')
101 and constants.CHROME_VERSION in view['attributes'].keys()):
102
103 return view['attributes'].get(constants.CHROME_VERSION)
104
105 logging.warning('Could not find chrome version for failure.')
106 return None
107
108
Simran Basi85f4c362014-04-08 13:40:57 -0700109def _lsbrelease_search(regex, group_id=0):
110 """Searches /etc/lsb-release for a regex match.
111
112 @param regex: Regex to match.
113 @param group_id: The group in the regex we are searching for.
114 Default is group 0.
115
116 @returns the string in the specified group if there is a match or None if
117 not found.
118
119 @raises IOError if /etc/lsb-release can not be accessed.
120 """
121 with open(constants.LSB_RELEASE) as lsb_release_file:
122 for line in lsb_release_file:
123 m = re.match(regex, line)
124 if m:
125 return m.group(group_id)
126 return None
127
128
Fang Deng7c2be102012-08-27 16:20:25 -0700129def get_current_board():
130 """Return the current board name.
131
132 @return current board name, e.g "lumpy", None on fail.
133 """
Simran Basi85f4c362014-04-08 13:40:57 -0700134 return _lsbrelease_search(r'^CHROMEOS_RELEASE_BOARD=(.+)$', group_id=1)
Simran Basi87d7a212012-09-27 10:41:05 -0700135
136
mussa9f6a0ae2014-06-05 14:54:05 -0700137def get_chromeos_release_version():
138 """
139 @return chromeos version in device under test as string. None on fail.
140 """
141 return _lsbrelease_search(r'^CHROMEOS_RELEASE_VERSION=(.+)$', group_id=1)
142
143
Simran Basi85f4c362014-04-08 13:40:57 -0700144def is_moblab():
145 """Return if we are running on a Moblab system or not.
146
147 @return the board string if this is a Moblab device or None if it is not.
148 """
149 try:
150 return _lsbrelease_search(r'.*moblab')
151 except IOError as e:
152 logging.error('Unable to determine if this is a moblab system: %s', e)
153
Simran Basidd129972014-09-11 14:34:49 -0700154
155def get_interface_mac_address(interface):
156 """Return the MAC address of a given interface.
157
158 @param interface: Interface to look up the MAC address of.
159 """
160 interface_link = base_utils.run(
161 'ip addr show %s | grep link/ether' % interface).stdout
162 # The output will be in the format of:
163 # 'link/ether <mac> brd ff:ff:ff:ff:ff:ff'
164 return interface_link.split()[1]
165
166
167def get_offload_gsuri():
168 """Return the GSURI to offload test results to.
169
170 For the normal use case this is the results_storage_server in the
171 global_config.
172
173 However partners using Moblab will be offloading their results to a
174 subdirectory of their image storage buckets. The subdirectory is
175 determined by the MAC Address of the Moblab device.
176
177 @returns gsuri to offload test results to.
178 """
179 if not is_moblab():
180 return DEFAULT_OFFLOAD_GSURI
181 moblab_id_filepath = '/home/moblab/.moblab_id'
182 if os.path.exists(moblab_id_filepath):
183 with open(moblab_id_filepath, 'r') as moblab_id_file:
184 random_id = moblab_id_file.read()
185 else:
186 random_id = uuid.uuid1()
187 with open(moblab_id_filepath, 'w') as moblab_id_file:
188 moblab_id_file.write('%s' % random_id)
189 return '%sresults/%s/%s/' % (
190 global_config.global_config.get_config_value(
191 'CROS', 'image_storage_server'),
192 get_interface_mac_address(MOBLAB_ETH), random_id)
193
194
Simran Basi87d7a212012-09-27 10:41:05 -0700195# TODO(petermayo): crosbug.com/31826 Share this with _GsUpload in
196# //chromite.git/buildbot/prebuilt.py somewhere/somehow
197def gs_upload(local_file, remote_file, acl, result_dir=None,
198 transfer_timeout=300, acl_timeout=300):
199 """Upload to GS bucket.
200
201 @param local_file: Local file to upload
202 @param remote_file: Remote location to upload the local_file to.
203 @param acl: name or file used for controlling access to the uploaded
204 file.
205 @param result_dir: Result directory if you want to add tracing to the
206 upload.
beepsfda8f412013-05-02 19:08:20 -0700207 @param transfer_timeout: Timeout for this upload call.
208 @param acl_timeout: Timeout for the acl call needed to confirm that
209 the uploader has permissions to execute the upload.
Simran Basi87d7a212012-09-27 10:41:05 -0700210
211 @raise CmdError: the exit code of the gsutil call was not 0.
212
213 @returns True/False - depending on if the upload succeeded or failed.
214 """
215 # https://developers.google.com/storage/docs/accesscontrol#extension
216 CANNED_ACLS = ['project-private', 'private', 'public-read',
217 'public-read-write', 'authenticated-read',
218 'bucket-owner-read', 'bucket-owner-full-control']
219 _GSUTIL_BIN = 'gsutil'
220 acl_cmd = None
221 if acl in CANNED_ACLS:
222 cmd = '%s cp -a %s %s %s' % (_GSUTIL_BIN, acl, local_file, remote_file)
223 else:
224 # For private uploads we assume that the overlay board is set up
225 # properly and a googlestore_acl.xml is present, if not this script
226 # errors
227 cmd = '%s cp -a private %s %s' % (_GSUTIL_BIN, local_file, remote_file)
228 if not os.path.exists(acl):
229 logging.error('Unable to find ACL File %s.', acl)
230 return False
231 acl_cmd = '%s setacl %s %s' % (_GSUTIL_BIN, acl, remote_file)
232 if not result_dir:
233 base_utils.run(cmd, timeout=transfer_timeout, verbose=True)
234 if acl_cmd:
235 base_utils.run(acl_cmd, timeout=acl_timeout, verbose=True)
236 return True
237 with open(os.path.join(result_dir, 'tracing'), 'w') as ftrace:
238 ftrace.write('Preamble\n')
239 base_utils.run(cmd, timeout=transfer_timeout, verbose=True,
240 stdout_tee=ftrace, stderr_tee=ftrace)
241 if acl_cmd:
242 ftrace.write('\nACL setting\n')
243 # Apply the passed in ACL xml file to the uploaded object.
244 base_utils.run(acl_cmd, timeout=acl_timeout, verbose=True,
245 stdout_tee=ftrace, stderr_tee=ftrace)
246 ftrace.write('Postamble\n')
247 return True
Simran Basiaf9b8e72012-10-12 15:02:36 -0700248
249
Gilad Arnold0ed760c2012-11-05 23:42:53 -0800250def gs_ls(uri_pattern):
251 """Returns a list of URIs that match a given pattern.
252
253 @param uri_pattern: a GS URI pattern, may contain wildcards
254
255 @return A list of URIs matching the given pattern.
256
257 @raise CmdError: the gsutil command failed.
258
259 """
260 gs_cmd = ' '.join(['gsutil', 'ls', uri_pattern])
261 result = base_utils.system_output(gs_cmd).splitlines()
262 return [path.rstrip() for path in result if path]
263
264
Simran Basiaf9b8e72012-10-12 15:02:36 -0700265def nuke_pids(pid_list, signal_queue=[signal.SIGTERM, signal.SIGKILL]):
266 """
267 Given a list of pid's, kill them via an esclating series of signals.
268
269 @param pid_list: List of PID's to kill.
270 @param signal_queue: Queue of signals to send the PID's to terminate them.
Prashanth Bcf731e32014-08-10 18:03:57 -0700271
272 @return: A mapping of the signal name to the number of processes it
273 was sent to.
Simran Basiaf9b8e72012-10-12 15:02:36 -0700274 """
Prashanth Bcf731e32014-08-10 18:03:57 -0700275 sig_count = {}
276 # Though this is slightly hacky it beats hardcoding names anyday.
277 sig_names = dict((k, v) for v, k in signal.__dict__.iteritems()
278 if v.startswith('SIG'))
Simran Basiaf9b8e72012-10-12 15:02:36 -0700279 for sig in signal_queue:
280 logging.debug('Sending signal %s to the following pids:', sig)
Prashanth Bcf731e32014-08-10 18:03:57 -0700281 sig_count[sig_names.get(sig, 'unknown_signal')] = len(pid_list)
Simran Basiaf9b8e72012-10-12 15:02:36 -0700282 for pid in pid_list:
283 logging.debug('Pid %d', pid)
284 try:
285 os.kill(pid, sig)
286 except OSError:
287 # The process may have died from a previous signal before we
288 # could kill it.
289 pass
Prashanth Bcf731e32014-08-10 18:03:57 -0700290 pid_list = [pid for pid in pid_list if base_utils.pid_is_alive(pid)]
291 if not pid_list:
292 break
Simran Basiaf9b8e72012-10-12 15:02:36 -0700293 time.sleep(CHECK_PID_IS_ALIVE_TIMEOUT)
294 failed_list = []
295 if signal.SIGKILL in signal_queue:
Prashanth Bcf731e32014-08-10 18:03:57 -0700296 return sig_count
Simran Basiaf9b8e72012-10-12 15:02:36 -0700297 for pid in pid_list:
298 if base_utils.pid_is_alive(pid):
299 failed_list.append('Could not kill %d for process name: %s.' % pid,
Simran Basi62723202013-01-22 15:24:49 -0800300 base_utils.get_process_name(pid))
Simran Basiaf9b8e72012-10-12 15:02:36 -0700301 if failed_list:
302 raise error.AutoservRunError('Following errors occured: %s' %
303 failed_list, None)
Prashanth Bcf731e32014-08-10 18:03:57 -0700304 return sig_count
Gilad Arnold0ed760c2012-11-05 23:42:53 -0800305
306
307def externalize_host(host):
308 """Returns an externally accessible host name.
309
310 @param host: a host name or address (string)
311
312 @return An externally visible host name or address
313
314 """
315 return socket.gethostname() if host in _LOCAL_HOST_LIST else host
Simran Basi22aa9fe2012-12-07 16:37:09 -0800316
317
beeps60aec242013-06-26 14:47:48 -0700318def urlopen_socket_timeout(url, data=None, timeout=5):
319 """
320 Wrapper to urllib2.urlopen with a socket timeout.
321
322 This method will convert all socket timeouts to
323 TimeoutExceptions, so we can use it in conjunction
324 with the rpc retry decorator and continue to handle
325 other URLErrors as we see fit.
326
327 @param url: The url to open.
328 @param data: The data to send to the url (eg: the urlencoded dictionary
329 used with a POST call).
330 @param timeout: The timeout for this urlopen call.
331
332 @return: The response of the urlopen call.
333
334 @raises: error.TimeoutException when a socket timeout occurs.
Dan Shi6c00dde2013-07-29 17:47:29 -0700335 urllib2.URLError for errors that not caused by timeout.
336 urllib2.HTTPError for errors like 404 url not found.
beeps60aec242013-06-26 14:47:48 -0700337 """
338 old_timeout = socket.getdefaulttimeout()
339 socket.setdefaulttimeout(timeout)
340 try:
341 return urllib2.urlopen(url, data=data)
342 except urllib2.URLError as e:
343 if type(e.reason) is socket.timeout:
344 raise error.TimeoutException(str(e))
Dan Shi6c00dde2013-07-29 17:47:29 -0700345 raise
beeps60aec242013-06-26 14:47:48 -0700346 finally:
347 socket.setdefaulttimeout(old_timeout)
Luis Lozano40b7d0d2014-01-17 15:12:06 -0800348
349
350def parse_chrome_version(version_string):
351 """
352 Parse a chrome version string and return version and milestone.
353
354 Given a chrome version of the form "W.X.Y.Z", return "W.X.Y.Z" as
355 the version and "W" as the milestone.
356
357 @param version_string: Chrome version string.
358 @return: a tuple (chrome_version, milestone). If the incoming version
359 string is not of the form "W.X.Y.Z", chrome_version will
360 be set to the incoming "version_string" argument and the
361 milestone will be set to the empty string.
362 """
363 match = re.search('(\d+)\.\d+\.\d+\.\d+', version_string)
364 ver = match.group(0) if match else version_string
365 milestone = match.group(1) if match else ''
366 return ver, milestone
beepsbff9f9d2013-12-06 11:14:08 -0800367
368
Dan Shif6c65bd2014-08-29 16:15:07 -0700369
370def is_localhost(server):
371 """Check if server is equivalent to localhost.
372
373 @param server: Name of the server to check.
374
375 @return: True if given server is equivalent to localhost.
376 @raise socket.gaierror: If server name failed to be resolved.
377 """
378 if server in _LOCAL_HOST_LIST:
379 return True
380 try:
381 return (socket.gethostbyname(socket.gethostname()) ==
382 socket.gethostbyname(server))
383 except socket.gaierror:
384 logging.error('Failed to resolve server name %s.', server)
385 return False