blob: 64f1e924be3b3d56979b9fd03c82131b9a481d7d [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
beepsbff9f9d2013-12-06 11:14:08 -08005import glob
Simran Basi87d7a212012-09-27 10:41:05 -07006import logging
Simran Basiaf9b8e72012-10-12 15:02:36 -07007import os
Fang Deng7c2be102012-08-27 16:20:25 -07008import re
Simran Basiaf9b8e72012-10-12 15:02:36 -07009import signal
Scott Zawalski347a0b82012-03-30 16:39:21 -040010import socket
beepsbff9f9d2013-12-06 11:14:08 -080011import sys
Simran Basiaf9b8e72012-10-12 15:02:36 -070012import time
beeps60aec242013-06-26 14:47:48 -070013import urllib2
Chris Masone6a0680f2012-03-02 08:40:00 -080014
Simran Basiaf9b8e72012-10-12 15:02:36 -070015from autotest_lib.client.common_lib import base_utils, error, global_config
beepsc4fb1472013-05-08 21:49:48 -070016from autotest_lib.client.cros import constants
Simran Basiaf9b8e72012-10-12 15:02:36 -070017
18
19# Keep checking if the pid is alive every second until the timeout (in seconds)
20CHECK_PID_IS_ALIVE_TIMEOUT = 6
21
Simran Basi22aa9fe2012-12-07 16:37:09 -080022_LOCAL_HOST_LIST = ('localhost', '127.0.0.1')
23
Fang Deng3197b392013-06-26 11:42:02 -070024
Chris Masone6a0680f2012-03-02 08:40:00 -080025def ping(host, deadline=None, tries=None, timeout=60):
26 """Attempt to ping |host|.
27
28 Shell out to 'ping' to try to reach |host| for |timeout| seconds.
29 Returns exit code of ping.
30
31 Per 'man ping', if you specify BOTH |deadline| and |tries|, ping only
32 returns 0 if we get responses to |tries| pings within |deadline| seconds.
33
34 Specifying |deadline| or |count| alone should return 0 as long as
35 some packets receive responses.
36
beepsfda8f412013-05-02 19:08:20 -070037 @param host: the host to ping.
Chris Masone6a0680f2012-03-02 08:40:00 -080038 @param deadline: seconds within which |tries| pings must succeed.
39 @param tries: number of pings to send.
40 @param timeout: number of seconds after which to kill 'ping' command.
41 @return exit code of ping command.
42 """
43 args = [host]
44 if deadline:
45 args.append('-w%d' % deadline)
46 if tries:
47 args.append('-c%d' % tries)
48 return base_utils.run('ping', args=args,
49 ignore_status=True, timeout=timeout,
Scott Zawalskiae843542012-03-20 09:51:29 -040050 stdout_tee=base_utils.TEE_TO_LOGS,
51 stderr_tee=base_utils.TEE_TO_LOGS).exit_status
Scott Zawalski347a0b82012-03-30 16:39:21 -040052
53
54def host_is_in_lab_zone(hostname):
55 """Check if the host is in the CROS.dns_zone.
56
57 @param hostname: The hostname to check.
58 @returns True if hostname.dns_zone resolves, otherwise False.
59 """
60 host_parts = hostname.split('.')
61 dns_zone = global_config.global_config.get_config_value('CROS', 'dns_zone',
62 default=None)
63 fqdn = '%s.%s' % (host_parts[0], dns_zone)
64 try:
65 socket.gethostbyname(fqdn)
66 return True
67 except socket.gaierror:
68 return False
Fang Deng7c2be102012-08-27 16:20:25 -070069
70
beepsc4fb1472013-05-08 21:49:48 -070071def get_chrome_version(job_views):
72 """
73 Retrieves the version of the chrome binary associated with a job.
74
75 When a test runs we query the chrome binary for it's version and drop
76 that value into a client keyval. To retrieve the chrome version we get all
77 the views associated with a test from the db, including those of the
78 server and client jobs, and parse the version out of the first test view
79 that has it. If we never ran a single test in the suite the job_views
80 dictionary will not contain a chrome version.
81
82 This method cannot retrieve the chrome version from a dictionary that
83 does not conform to the structure of an autotest tko view.
84
85 @param job_views: a list of a job's result views, as returned by
86 the get_detailed_test_views method in rpc_interface.
87 @return: The chrome version string, or None if one can't be found.
88 """
89
90 # Aborted jobs have no views.
91 if not job_views:
92 return None
93
94 for view in job_views:
95 if (view.get('attributes')
96 and constants.CHROME_VERSION in view['attributes'].keys()):
97
98 return view['attributes'].get(constants.CHROME_VERSION)
99
100 logging.warning('Could not find chrome version for failure.')
101 return None
102
103
Simran Basi85f4c362014-04-08 13:40:57 -0700104def _lsbrelease_search(regex, group_id=0):
105 """Searches /etc/lsb-release for a regex match.
106
107 @param regex: Regex to match.
108 @param group_id: The group in the regex we are searching for.
109 Default is group 0.
110
111 @returns the string in the specified group if there is a match or None if
112 not found.
113
114 @raises IOError if /etc/lsb-release can not be accessed.
115 """
116 with open(constants.LSB_RELEASE) as lsb_release_file:
117 for line in lsb_release_file:
118 m = re.match(regex, line)
119 if m:
120 return m.group(group_id)
121 return None
122
123
Fang Deng7c2be102012-08-27 16:20:25 -0700124def get_current_board():
125 """Return the current board name.
126
127 @return current board name, e.g "lumpy", None on fail.
128 """
Simran Basi85f4c362014-04-08 13:40:57 -0700129 return _lsbrelease_search(r'^CHROMEOS_RELEASE_BOARD=(.+)$', group_id=1)
Simran Basi87d7a212012-09-27 10:41:05 -0700130
131
mussa9f6a0ae2014-06-05 14:54:05 -0700132def get_chromeos_release_version():
133 """
134 @return chromeos version in device under test as string. None on fail.
135 """
136 return _lsbrelease_search(r'^CHROMEOS_RELEASE_VERSION=(.+)$', group_id=1)
137
138
Simran Basi85f4c362014-04-08 13:40:57 -0700139def is_moblab():
140 """Return if we are running on a Moblab system or not.
141
142 @return the board string if this is a Moblab device or None if it is not.
143 """
144 try:
145 return _lsbrelease_search(r'.*moblab')
146 except IOError as e:
147 logging.error('Unable to determine if this is a moblab system: %s', e)
148
Simran Basi87d7a212012-09-27 10:41:05 -0700149# TODO(petermayo): crosbug.com/31826 Share this with _GsUpload in
150# //chromite.git/buildbot/prebuilt.py somewhere/somehow
151def gs_upload(local_file, remote_file, acl, result_dir=None,
152 transfer_timeout=300, acl_timeout=300):
153 """Upload to GS bucket.
154
155 @param local_file: Local file to upload
156 @param remote_file: Remote location to upload the local_file to.
157 @param acl: name or file used for controlling access to the uploaded
158 file.
159 @param result_dir: Result directory if you want to add tracing to the
160 upload.
beepsfda8f412013-05-02 19:08:20 -0700161 @param transfer_timeout: Timeout for this upload call.
162 @param acl_timeout: Timeout for the acl call needed to confirm that
163 the uploader has permissions to execute the upload.
Simran Basi87d7a212012-09-27 10:41:05 -0700164
165 @raise CmdError: the exit code of the gsutil call was not 0.
166
167 @returns True/False - depending on if the upload succeeded or failed.
168 """
169 # https://developers.google.com/storage/docs/accesscontrol#extension
170 CANNED_ACLS = ['project-private', 'private', 'public-read',
171 'public-read-write', 'authenticated-read',
172 'bucket-owner-read', 'bucket-owner-full-control']
173 _GSUTIL_BIN = 'gsutil'
174 acl_cmd = None
175 if acl in CANNED_ACLS:
176 cmd = '%s cp -a %s %s %s' % (_GSUTIL_BIN, acl, local_file, remote_file)
177 else:
178 # For private uploads we assume that the overlay board is set up
179 # properly and a googlestore_acl.xml is present, if not this script
180 # errors
181 cmd = '%s cp -a private %s %s' % (_GSUTIL_BIN, local_file, remote_file)
182 if not os.path.exists(acl):
183 logging.error('Unable to find ACL File %s.', acl)
184 return False
185 acl_cmd = '%s setacl %s %s' % (_GSUTIL_BIN, acl, remote_file)
186 if not result_dir:
187 base_utils.run(cmd, timeout=transfer_timeout, verbose=True)
188 if acl_cmd:
189 base_utils.run(acl_cmd, timeout=acl_timeout, verbose=True)
190 return True
191 with open(os.path.join(result_dir, 'tracing'), 'w') as ftrace:
192 ftrace.write('Preamble\n')
193 base_utils.run(cmd, timeout=transfer_timeout, verbose=True,
194 stdout_tee=ftrace, stderr_tee=ftrace)
195 if acl_cmd:
196 ftrace.write('\nACL setting\n')
197 # Apply the passed in ACL xml file to the uploaded object.
198 base_utils.run(acl_cmd, timeout=acl_timeout, verbose=True,
199 stdout_tee=ftrace, stderr_tee=ftrace)
200 ftrace.write('Postamble\n')
201 return True
Simran Basiaf9b8e72012-10-12 15:02:36 -0700202
203
Gilad Arnold0ed760c2012-11-05 23:42:53 -0800204def gs_ls(uri_pattern):
205 """Returns a list of URIs that match a given pattern.
206
207 @param uri_pattern: a GS URI pattern, may contain wildcards
208
209 @return A list of URIs matching the given pattern.
210
211 @raise CmdError: the gsutil command failed.
212
213 """
214 gs_cmd = ' '.join(['gsutil', 'ls', uri_pattern])
215 result = base_utils.system_output(gs_cmd).splitlines()
216 return [path.rstrip() for path in result if path]
217
218
Simran Basiaf9b8e72012-10-12 15:02:36 -0700219def nuke_pids(pid_list, signal_queue=[signal.SIGTERM, signal.SIGKILL]):
220 """
221 Given a list of pid's, kill them via an esclating series of signals.
222
223 @param pid_list: List of PID's to kill.
224 @param signal_queue: Queue of signals to send the PID's to terminate them.
225 """
226 for sig in signal_queue:
227 logging.debug('Sending signal %s to the following pids:', sig)
228 for pid in pid_list:
229 logging.debug('Pid %d', pid)
230 try:
231 os.kill(pid, sig)
232 except OSError:
233 # The process may have died from a previous signal before we
234 # could kill it.
235 pass
236 time.sleep(CHECK_PID_IS_ALIVE_TIMEOUT)
237 failed_list = []
238 if signal.SIGKILL in signal_queue:
239 return
240 for pid in pid_list:
241 if base_utils.pid_is_alive(pid):
242 failed_list.append('Could not kill %d for process name: %s.' % pid,
Simran Basi62723202013-01-22 15:24:49 -0800243 base_utils.get_process_name(pid))
Simran Basiaf9b8e72012-10-12 15:02:36 -0700244 if failed_list:
245 raise error.AutoservRunError('Following errors occured: %s' %
246 failed_list, None)
Gilad Arnold0ed760c2012-11-05 23:42:53 -0800247
248
249def externalize_host(host):
250 """Returns an externally accessible host name.
251
252 @param host: a host name or address (string)
253
254 @return An externally visible host name or address
255
256 """
257 return socket.gethostname() if host in _LOCAL_HOST_LIST else host
Simran Basi22aa9fe2012-12-07 16:37:09 -0800258
259
beeps60aec242013-06-26 14:47:48 -0700260def urlopen_socket_timeout(url, data=None, timeout=5):
261 """
262 Wrapper to urllib2.urlopen with a socket timeout.
263
264 This method will convert all socket timeouts to
265 TimeoutExceptions, so we can use it in conjunction
266 with the rpc retry decorator and continue to handle
267 other URLErrors as we see fit.
268
269 @param url: The url to open.
270 @param data: The data to send to the url (eg: the urlencoded dictionary
271 used with a POST call).
272 @param timeout: The timeout for this urlopen call.
273
274 @return: The response of the urlopen call.
275
276 @raises: error.TimeoutException when a socket timeout occurs.
Dan Shi6c00dde2013-07-29 17:47:29 -0700277 urllib2.URLError for errors that not caused by timeout.
278 urllib2.HTTPError for errors like 404 url not found.
beeps60aec242013-06-26 14:47:48 -0700279 """
280 old_timeout = socket.getdefaulttimeout()
281 socket.setdefaulttimeout(timeout)
282 try:
283 return urllib2.urlopen(url, data=data)
284 except urllib2.URLError as e:
285 if type(e.reason) is socket.timeout:
286 raise error.TimeoutException(str(e))
Dan Shi6c00dde2013-07-29 17:47:29 -0700287 raise
beeps60aec242013-06-26 14:47:48 -0700288 finally:
289 socket.setdefaulttimeout(old_timeout)
Luis Lozano40b7d0d2014-01-17 15:12:06 -0800290
291
292def parse_chrome_version(version_string):
293 """
294 Parse a chrome version string and return version and milestone.
295
296 Given a chrome version of the form "W.X.Y.Z", return "W.X.Y.Z" as
297 the version and "W" as the milestone.
298
299 @param version_string: Chrome version string.
300 @return: a tuple (chrome_version, milestone). If the incoming version
301 string is not of the form "W.X.Y.Z", chrome_version will
302 be set to the incoming "version_string" argument and the
303 milestone will be set to the empty string.
304 """
305 match = re.search('(\d+)\.\d+\.\d+\.\d+', version_string)
306 ver = match.group(0) if match else version_string
307 milestone = match.group(1) if match else ''
308 return ver, milestone
beepsbff9f9d2013-12-06 11:14:08 -0800309
310
311def take_screenshot(dest_dir, fname_prefix, format='png'):
mussafd5b8052014-05-06 10:04:54 -0700312 """
313 Take screenshot and save to a new file in the dest_dir.
beepsbff9f9d2013-12-06 11:14:08 -0800314
mussafd5b8052014-05-06 10:04:54 -0700315 @param dest_dir: path, destination directory to save the screenshot.
316 @param fname_prefix: string, prefix for output filename.
317 @param format: string, file format ('png', 'jpg', etc) to use.
beepsbff9f9d2013-12-06 11:14:08 -0800318
mussafd5b8052014-05-06 10:04:54 -0700319 @returns complete path to saved screenshot file.
320
beepsbff9f9d2013-12-06 11:14:08 -0800321 """
Dan Shi1d8803b2014-06-19 14:32:00 -0700322 if not _is_x_running():
323 return
324
beepsbff9f9d2013-12-06 11:14:08 -0800325 next_index = len(glob.glob(
326 os.path.join(dest_dir, '%s-*.%s' % (fname_prefix, format))))
327 screenshot_file = os.path.join(
328 dest_dir, '%s-%d.%s' % (fname_prefix, next_index, format))
329 logging.info('Saving screenshot to %s.', screenshot_file)
330
mussafd5b8052014-05-06 10:04:54 -0700331 import_cmd = ('/usr/local/bin/import -window root -depth 8 %s' %
332 screenshot_file)
333
334 _execute_screenshot_capture_command(import_cmd)
335
336 return screenshot_file
337
338
339def take_screen_shot_crop_by_height(fullpath, final_height, x_offset_pixels,
340 y_offset_pixels):
341 """
342 Take a screenshot, crop to final height starting at given (x, y) coordinate.
343
344 Image width will be adjusted to maintain original aspect ratio).
345
346 @param fullpath: path, fullpath of the file that will become the image file.
347 @param final_height: integer, height in pixels of resulting image.
348 @param x_offset_pixels: integer, number of pixels from left margin
349 to begin cropping.
350 @param y_offset_pixels: integer, number of pixels from top margin
351 to begin cropping.
352
353 """
354
355 params = {'height': final_height, 'x_offset': x_offset_pixels,
356 'y_offset': y_offset_pixels, 'path': fullpath}
357
358 import_cmd = ('/usr/local/bin/import -window root -depth 8 -crop '
359 'x%(height)d+%(x_offset)d+%(y_offset)d %(path)s' % params)
360
361 _execute_screenshot_capture_command(import_cmd)
362
363 return fullpath
364
365
366def _execute_screenshot_capture_command(import_cmd_string):
367 """
368 Executes command to capture a screenshot.
369
370 Provides safe execution of command to capture screenshot by wrapping
371 the command around a try-catch construct.
372
373 @param import_cmd_string: string, screenshot capture command.
374
375 """
376
beepsbff9f9d2013-12-06 11:14:08 -0800377 old_exc_type = sys.exc_info()[0]
mussafd5b8052014-05-06 10:04:54 -0700378 full_cmd = ('DISPLAY=:0.0 XAUTHORITY=/home/chronos/.Xauthority %s' %
379 import_cmd_string)
beepsbff9f9d2013-12-06 11:14:08 -0800380 try:
mussafd5b8052014-05-06 10:04:54 -0700381 base_utils.system(full_cmd)
beepsbff9f9d2013-12-06 11:14:08 -0800382 except Exception as err:
383 # Do not raise an exception if the screenshot fails while processing
384 # another exception.
385 if old_exc_type is None:
386 raise
Dan Shi1d8803b2014-06-19 14:32:00 -0700387 logging.error(err)
388
389
390def _is_x_running():
391 try:
392 return int(base_utils.system_output('pgrep -o ^X$')) > 0
393 except Exception:
394 return False