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