blob: b3a0b3a6d98bdcd76b834e627ea78f4bacda07b4 [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
Dan Shi60cf6a92015-01-29 17:22:49 -080014from autotest_lib.client.common_lib import base_utils
15from autotest_lib.client.common_lib import error
16from autotest_lib.client.common_lib import global_config
17from autotest_lib.client.common_lib import lsbrelease_utils
beepsc4fb1472013-05-08 21:49:48 -070018from autotest_lib.client.cros import constants
Simran Basiaf9b8e72012-10-12 15:02:36 -070019
20
21# Keep checking if the pid is alive every second until the timeout (in seconds)
22CHECK_PID_IS_ALIVE_TIMEOUT = 6
23
Simran Basi22aa9fe2012-12-07 16:37:09 -080024_LOCAL_HOST_LIST = ('localhost', '127.0.0.1')
25
Prashanth Balasubramanian6edaaf92014-11-24 16:36:25 -080026# The default address of a vm gateway.
27DEFAULT_VM_GATEWAY = '10.0.2.2'
28
Simran Basidd129972014-09-11 14:34:49 -070029# Google Storage bucket URI to store results in.
30DEFAULT_OFFLOAD_GSURI = global_config.global_config.get_config_value(
31 'CROS', 'results_storage_server', default=None)
32
33# Default Moblab Ethernet Interface.
34MOBLAB_ETH = 'eth0'
Fang Deng3197b392013-06-26 11:42:02 -070035
Chris Masone6a0680f2012-03-02 08:40:00 -080036def ping(host, deadline=None, tries=None, timeout=60):
37 """Attempt to ping |host|.
38
39 Shell out to 'ping' to try to reach |host| for |timeout| seconds.
40 Returns exit code of ping.
41
42 Per 'man ping', if you specify BOTH |deadline| and |tries|, ping only
43 returns 0 if we get responses to |tries| pings within |deadline| seconds.
44
45 Specifying |deadline| or |count| alone should return 0 as long as
46 some packets receive responses.
47
beepsfda8f412013-05-02 19:08:20 -070048 @param host: the host to ping.
Chris Masone6a0680f2012-03-02 08:40:00 -080049 @param deadline: seconds within which |tries| pings must succeed.
50 @param tries: number of pings to send.
51 @param timeout: number of seconds after which to kill 'ping' command.
52 @return exit code of ping command.
53 """
54 args = [host]
55 if deadline:
56 args.append('-w%d' % deadline)
57 if tries:
58 args.append('-c%d' % tries)
59 return base_utils.run('ping', args=args,
60 ignore_status=True, timeout=timeout,
Scott Zawalskiae843542012-03-20 09:51:29 -040061 stdout_tee=base_utils.TEE_TO_LOGS,
62 stderr_tee=base_utils.TEE_TO_LOGS).exit_status
Scott Zawalski347a0b82012-03-30 16:39:21 -040063
64
65def host_is_in_lab_zone(hostname):
Hung-ying Tyancbdd1982014-09-03 16:54:08 +080066 """Check if the host is in the CLIENT.dns_zone.
Scott Zawalski347a0b82012-03-30 16:39:21 -040067
68 @param hostname: The hostname to check.
69 @returns True if hostname.dns_zone resolves, otherwise False.
70 """
71 host_parts = hostname.split('.')
Hung-ying Tyancbdd1982014-09-03 16:54:08 +080072 dns_zone = global_config.global_config.get_config_value('CLIENT', 'dns_zone',
Scott Zawalski347a0b82012-03-30 16:39:21 -040073 default=None)
74 fqdn = '%s.%s' % (host_parts[0], dns_zone)
75 try:
76 socket.gethostbyname(fqdn)
77 return True
78 except socket.gaierror:
Ilja H. Friedel8753d832014-10-07 22:30:06 -070079 return False
Fang Deng7c2be102012-08-27 16:20:25 -070080
81
beepsc4fb1472013-05-08 21:49:48 -070082def get_chrome_version(job_views):
83 """
84 Retrieves the version of the chrome binary associated with a job.
85
86 When a test runs we query the chrome binary for it's version and drop
87 that value into a client keyval. To retrieve the chrome version we get all
88 the views associated with a test from the db, including those of the
89 server and client jobs, and parse the version out of the first test view
90 that has it. If we never ran a single test in the suite the job_views
91 dictionary will not contain a chrome version.
92
93 This method cannot retrieve the chrome version from a dictionary that
94 does not conform to the structure of an autotest tko view.
95
96 @param job_views: a list of a job's result views, as returned by
97 the get_detailed_test_views method in rpc_interface.
98 @return: The chrome version string, or None if one can't be found.
99 """
100
101 # Aborted jobs have no views.
102 if not job_views:
103 return None
104
105 for view in job_views:
106 if (view.get('attributes')
107 and constants.CHROME_VERSION in view['attributes'].keys()):
108
109 return view['attributes'].get(constants.CHROME_VERSION)
110
111 logging.warning('Could not find chrome version for failure.')
112 return None
113
114
Simran Basidd129972014-09-11 14:34:49 -0700115def get_interface_mac_address(interface):
116 """Return the MAC address of a given interface.
117
118 @param interface: Interface to look up the MAC address of.
119 """
120 interface_link = base_utils.run(
121 'ip addr show %s | grep link/ether' % interface).stdout
122 # The output will be in the format of:
123 # 'link/ether <mac> brd ff:ff:ff:ff:ff:ff'
124 return interface_link.split()[1]
125
126
127def get_offload_gsuri():
128 """Return the GSURI to offload test results to.
129
130 For the normal use case this is the results_storage_server in the
131 global_config.
132
133 However partners using Moblab will be offloading their results to a
134 subdirectory of their image storage buckets. The subdirectory is
135 determined by the MAC Address of the Moblab device.
136
137 @returns gsuri to offload test results to.
138 """
Dan Shi60cf6a92015-01-29 17:22:49 -0800139 if not lsbrelease_utils.is_moblab():
Simran Basidd129972014-09-11 14:34:49 -0700140 return DEFAULT_OFFLOAD_GSURI
141 moblab_id_filepath = '/home/moblab/.moblab_id'
142 if os.path.exists(moblab_id_filepath):
143 with open(moblab_id_filepath, 'r') as moblab_id_file:
144 random_id = moblab_id_file.read()
145 else:
146 random_id = uuid.uuid1()
147 with open(moblab_id_filepath, 'w') as moblab_id_file:
148 moblab_id_file.write('%s' % random_id)
149 return '%sresults/%s/%s/' % (
150 global_config.global_config.get_config_value(
151 'CROS', 'image_storage_server'),
152 get_interface_mac_address(MOBLAB_ETH), random_id)
153
154
Simran Basi87d7a212012-09-27 10:41:05 -0700155# TODO(petermayo): crosbug.com/31826 Share this with _GsUpload in
156# //chromite.git/buildbot/prebuilt.py somewhere/somehow
157def gs_upload(local_file, remote_file, acl, result_dir=None,
158 transfer_timeout=300, acl_timeout=300):
159 """Upload to GS bucket.
160
161 @param local_file: Local file to upload
162 @param remote_file: Remote location to upload the local_file to.
163 @param acl: name or file used for controlling access to the uploaded
164 file.
165 @param result_dir: Result directory if you want to add tracing to the
166 upload.
beepsfda8f412013-05-02 19:08:20 -0700167 @param transfer_timeout: Timeout for this upload call.
168 @param acl_timeout: Timeout for the acl call needed to confirm that
169 the uploader has permissions to execute the upload.
Simran Basi87d7a212012-09-27 10:41:05 -0700170
171 @raise CmdError: the exit code of the gsutil call was not 0.
172
173 @returns True/False - depending on if the upload succeeded or failed.
174 """
175 # https://developers.google.com/storage/docs/accesscontrol#extension
176 CANNED_ACLS = ['project-private', 'private', 'public-read',
177 'public-read-write', 'authenticated-read',
178 'bucket-owner-read', 'bucket-owner-full-control']
179 _GSUTIL_BIN = 'gsutil'
180 acl_cmd = None
181 if acl in CANNED_ACLS:
182 cmd = '%s cp -a %s %s %s' % (_GSUTIL_BIN, acl, local_file, remote_file)
183 else:
184 # For private uploads we assume that the overlay board is set up
185 # properly and a googlestore_acl.xml is present, if not this script
186 # errors
187 cmd = '%s cp -a private %s %s' % (_GSUTIL_BIN, local_file, remote_file)
188 if not os.path.exists(acl):
189 logging.error('Unable to find ACL File %s.', acl)
190 return False
191 acl_cmd = '%s setacl %s %s' % (_GSUTIL_BIN, acl, remote_file)
192 if not result_dir:
193 base_utils.run(cmd, timeout=transfer_timeout, verbose=True)
194 if acl_cmd:
195 base_utils.run(acl_cmd, timeout=acl_timeout, verbose=True)
196 return True
197 with open(os.path.join(result_dir, 'tracing'), 'w') as ftrace:
198 ftrace.write('Preamble\n')
199 base_utils.run(cmd, timeout=transfer_timeout, verbose=True,
200 stdout_tee=ftrace, stderr_tee=ftrace)
201 if acl_cmd:
202 ftrace.write('\nACL setting\n')
203 # Apply the passed in ACL xml file to the uploaded object.
204 base_utils.run(acl_cmd, timeout=acl_timeout, verbose=True,
205 stdout_tee=ftrace, stderr_tee=ftrace)
206 ftrace.write('Postamble\n')
207 return True
Simran Basiaf9b8e72012-10-12 15:02:36 -0700208
209
Gilad Arnold0ed760c2012-11-05 23:42:53 -0800210def gs_ls(uri_pattern):
211 """Returns a list of URIs that match a given pattern.
212
213 @param uri_pattern: a GS URI pattern, may contain wildcards
214
215 @return A list of URIs matching the given pattern.
216
217 @raise CmdError: the gsutil command failed.
218
219 """
220 gs_cmd = ' '.join(['gsutil', 'ls', uri_pattern])
221 result = base_utils.system_output(gs_cmd).splitlines()
222 return [path.rstrip() for path in result if path]
223
224
Simran Basiaf9b8e72012-10-12 15:02:36 -0700225def nuke_pids(pid_list, signal_queue=[signal.SIGTERM, signal.SIGKILL]):
226 """
227 Given a list of pid's, kill them via an esclating series of signals.
228
229 @param pid_list: List of PID's to kill.
230 @param signal_queue: Queue of signals to send the PID's to terminate them.
Prashanth Bcf731e32014-08-10 18:03:57 -0700231
232 @return: A mapping of the signal name to the number of processes it
233 was sent to.
Simran Basiaf9b8e72012-10-12 15:02:36 -0700234 """
Prashanth Bcf731e32014-08-10 18:03:57 -0700235 sig_count = {}
236 # Though this is slightly hacky it beats hardcoding names anyday.
237 sig_names = dict((k, v) for v, k in signal.__dict__.iteritems()
238 if v.startswith('SIG'))
Simran Basiaf9b8e72012-10-12 15:02:36 -0700239 for sig in signal_queue:
240 logging.debug('Sending signal %s to the following pids:', sig)
Prashanth Bcf731e32014-08-10 18:03:57 -0700241 sig_count[sig_names.get(sig, 'unknown_signal')] = len(pid_list)
Simran Basiaf9b8e72012-10-12 15:02:36 -0700242 for pid in pid_list:
243 logging.debug('Pid %d', pid)
244 try:
245 os.kill(pid, sig)
246 except OSError:
247 # The process may have died from a previous signal before we
248 # could kill it.
249 pass
David Jamesf77198b2014-11-17 17:23:12 -0800250 if sig == signal.SIGKILL:
251 return sig_count
Prashanth Bcf731e32014-08-10 18:03:57 -0700252 pid_list = [pid for pid in pid_list if base_utils.pid_is_alive(pid)]
253 if not pid_list:
254 break
Simran Basiaf9b8e72012-10-12 15:02:36 -0700255 time.sleep(CHECK_PID_IS_ALIVE_TIMEOUT)
256 failed_list = []
Simran Basiaf9b8e72012-10-12 15:02:36 -0700257 for pid in pid_list:
258 if base_utils.pid_is_alive(pid):
259 failed_list.append('Could not kill %d for process name: %s.' % pid,
Simran Basi62723202013-01-22 15:24:49 -0800260 base_utils.get_process_name(pid))
Simran Basiaf9b8e72012-10-12 15:02:36 -0700261 if failed_list:
262 raise error.AutoservRunError('Following errors occured: %s' %
263 failed_list, None)
Prashanth Bcf731e32014-08-10 18:03:57 -0700264 return sig_count
Gilad Arnold0ed760c2012-11-05 23:42:53 -0800265
266
267def externalize_host(host):
268 """Returns an externally accessible host name.
269
270 @param host: a host name or address (string)
271
272 @return An externally visible host name or address
273
274 """
275 return socket.gethostname() if host in _LOCAL_HOST_LIST else host
Simran Basi22aa9fe2012-12-07 16:37:09 -0800276
277
beeps60aec242013-06-26 14:47:48 -0700278def urlopen_socket_timeout(url, data=None, timeout=5):
279 """
280 Wrapper to urllib2.urlopen with a socket timeout.
281
282 This method will convert all socket timeouts to
283 TimeoutExceptions, so we can use it in conjunction
284 with the rpc retry decorator and continue to handle
285 other URLErrors as we see fit.
286
287 @param url: The url to open.
288 @param data: The data to send to the url (eg: the urlencoded dictionary
289 used with a POST call).
290 @param timeout: The timeout for this urlopen call.
291
292 @return: The response of the urlopen call.
293
294 @raises: error.TimeoutException when a socket timeout occurs.
Dan Shi6c00dde2013-07-29 17:47:29 -0700295 urllib2.URLError for errors that not caused by timeout.
296 urllib2.HTTPError for errors like 404 url not found.
beeps60aec242013-06-26 14:47:48 -0700297 """
298 old_timeout = socket.getdefaulttimeout()
299 socket.setdefaulttimeout(timeout)
300 try:
301 return urllib2.urlopen(url, data=data)
302 except urllib2.URLError as e:
303 if type(e.reason) is socket.timeout:
304 raise error.TimeoutException(str(e))
Dan Shi6c00dde2013-07-29 17:47:29 -0700305 raise
beeps60aec242013-06-26 14:47:48 -0700306 finally:
307 socket.setdefaulttimeout(old_timeout)
Luis Lozano40b7d0d2014-01-17 15:12:06 -0800308
309
310def parse_chrome_version(version_string):
311 """
312 Parse a chrome version string and return version and milestone.
313
314 Given a chrome version of the form "W.X.Y.Z", return "W.X.Y.Z" as
315 the version and "W" as the milestone.
316
317 @param version_string: Chrome version string.
318 @return: a tuple (chrome_version, milestone). If the incoming version
319 string is not of the form "W.X.Y.Z", chrome_version will
320 be set to the incoming "version_string" argument and the
321 milestone will be set to the empty string.
322 """
323 match = re.search('(\d+)\.\d+\.\d+\.\d+', version_string)
324 ver = match.group(0) if match else version_string
325 milestone = match.group(1) if match else ''
326 return ver, milestone
beepsbff9f9d2013-12-06 11:14:08 -0800327
328
Dan Shif6c65bd2014-08-29 16:15:07 -0700329
330def is_localhost(server):
331 """Check if server is equivalent to localhost.
332
333 @param server: Name of the server to check.
334
335 @return: True if given server is equivalent to localhost.
336 @raise socket.gaierror: If server name failed to be resolved.
337 """
338 if server in _LOCAL_HOST_LIST:
339 return True
340 try:
341 return (socket.gethostbyname(socket.gethostname()) ==
342 socket.gethostbyname(server))
343 except socket.gaierror:
344 logging.error('Failed to resolve server name %s.', server)
345 return False