Chris Masone | 6a0680f | 2012-03-02 08:40:00 -0800 | [diff] [blame] | 1 | # 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 Barnette | 3cbd76b | 2013-11-27 12:11:25 -0800 | [diff] [blame] | 4 | |
Dan Shi | 767dced | 2015-02-01 00:21:07 -0800 | [diff] [blame] | 5 | import inspect |
Simran Basi | 87d7a21 | 2012-09-27 10:41:05 -0700 | [diff] [blame] | 6 | import logging |
Simran Basi | af9b8e7 | 2012-10-12 15:02:36 -0700 | [diff] [blame] | 7 | import os |
Fang Deng | 7c2be10 | 2012-08-27 16:20:25 -0700 | [diff] [blame] | 8 | import re |
Simran Basi | af9b8e7 | 2012-10-12 15:02:36 -0700 | [diff] [blame] | 9 | import signal |
Scott Zawalski | 347a0b8 | 2012-03-30 16:39:21 -0400 | [diff] [blame] | 10 | import socket |
Simran Basi | af9b8e7 | 2012-10-12 15:02:36 -0700 | [diff] [blame] | 11 | import time |
beeps | 60aec24 | 2013-06-26 14:47:48 -0700 | [diff] [blame] | 12 | import urllib2 |
Simran Basi | dd12997 | 2014-09-11 14:34:49 -0700 | [diff] [blame] | 13 | import uuid |
Chris Masone | 6a0680f | 2012-03-02 08:40:00 -0800 | [diff] [blame] | 14 | |
Dan Shi | 60cf6a9 | 2015-01-29 17:22:49 -0800 | [diff] [blame] | 15 | from autotest_lib.client.common_lib import base_utils |
| 16 | from autotest_lib.client.common_lib import error |
| 17 | from autotest_lib.client.common_lib import global_config |
| 18 | from autotest_lib.client.common_lib import lsbrelease_utils |
Hsinyu Chao | e0b08e6 | 2015-08-11 10:50:37 +0000 | [diff] [blame^] | 19 | from autotest_lib.client.cros import constants |
Simran Basi | af9b8e7 | 2012-10-12 15:02:36 -0700 | [diff] [blame] | 20 | |
| 21 | |
| 22 | # Keep checking if the pid is alive every second until the timeout (in seconds) |
| 23 | CHECK_PID_IS_ALIVE_TIMEOUT = 6 |
| 24 | |
Simran Basi | 22aa9fe | 2012-12-07 16:37:09 -0800 | [diff] [blame] | 25 | _LOCAL_HOST_LIST = ('localhost', '127.0.0.1') |
| 26 | |
Prashanth Balasubramanian | 6edaaf9 | 2014-11-24 16:36:25 -0800 | [diff] [blame] | 27 | # The default address of a vm gateway. |
| 28 | DEFAULT_VM_GATEWAY = '10.0.2.2' |
| 29 | |
Simran Basi | dd12997 | 2014-09-11 14:34:49 -0700 | [diff] [blame] | 30 | # Google Storage bucket URI to store results in. |
| 31 | DEFAULT_OFFLOAD_GSURI = global_config.global_config.get_config_value( |
| 32 | 'CROS', 'results_storage_server', default=None) |
| 33 | |
| 34 | # Default Moblab Ethernet Interface. |
| 35 | MOBLAB_ETH = 'eth0' |
Fang Deng | 3197b39 | 2013-06-26 11:42:02 -0700 | [diff] [blame] | 36 | |
Chris Masone | 6a0680f | 2012-03-02 08:40:00 -0800 | [diff] [blame] | 37 | def ping(host, deadline=None, tries=None, timeout=60): |
| 38 | """Attempt to ping |host|. |
| 39 | |
| 40 | Shell out to 'ping' to try to reach |host| for |timeout| seconds. |
| 41 | Returns exit code of ping. |
| 42 | |
| 43 | Per 'man ping', if you specify BOTH |deadline| and |tries|, ping only |
| 44 | returns 0 if we get responses to |tries| pings within |deadline| seconds. |
| 45 | |
| 46 | Specifying |deadline| or |count| alone should return 0 as long as |
| 47 | some packets receive responses. |
| 48 | |
beeps | fda8f41 | 2013-05-02 19:08:20 -0700 | [diff] [blame] | 49 | @param host: the host to ping. |
Chris Masone | 6a0680f | 2012-03-02 08:40:00 -0800 | [diff] [blame] | 50 | @param deadline: seconds within which |tries| pings must succeed. |
| 51 | @param tries: number of pings to send. |
| 52 | @param timeout: number of seconds after which to kill 'ping' command. |
| 53 | @return exit code of ping command. |
| 54 | """ |
| 55 | args = [host] |
| 56 | if deadline: |
| 57 | args.append('-w%d' % deadline) |
| 58 | if tries: |
| 59 | args.append('-c%d' % tries) |
| 60 | return base_utils.run('ping', args=args, |
| 61 | ignore_status=True, timeout=timeout, |
Scott Zawalski | ae84354 | 2012-03-20 09:51:29 -0400 | [diff] [blame] | 62 | stdout_tee=base_utils.TEE_TO_LOGS, |
| 63 | stderr_tee=base_utils.TEE_TO_LOGS).exit_status |
Scott Zawalski | 347a0b8 | 2012-03-30 16:39:21 -0400 | [diff] [blame] | 64 | |
| 65 | |
| 66 | def host_is_in_lab_zone(hostname): |
Hung-ying Tyan | cbdd198 | 2014-09-03 16:54:08 +0800 | [diff] [blame] | 67 | """Check if the host is in the CLIENT.dns_zone. |
Scott Zawalski | 347a0b8 | 2012-03-30 16:39:21 -0400 | [diff] [blame] | 68 | |
| 69 | @param hostname: The hostname to check. |
| 70 | @returns True if hostname.dns_zone resolves, otherwise False. |
| 71 | """ |
| 72 | host_parts = hostname.split('.') |
Hung-ying Tyan | cbdd198 | 2014-09-03 16:54:08 +0800 | [diff] [blame] | 73 | dns_zone = global_config.global_config.get_config_value('CLIENT', 'dns_zone', |
Scott Zawalski | 347a0b8 | 2012-03-30 16:39:21 -0400 | [diff] [blame] | 74 | default=None) |
| 75 | fqdn = '%s.%s' % (host_parts[0], dns_zone) |
| 76 | try: |
| 77 | socket.gethostbyname(fqdn) |
| 78 | return True |
| 79 | except socket.gaierror: |
Ilja H. Friedel | 8753d83 | 2014-10-07 22:30:06 -0700 | [diff] [blame] | 80 | return False |
Fang Deng | 7c2be10 | 2012-08-27 16:20:25 -0700 | [diff] [blame] | 81 | |
| 82 | |
mukesh agrawal | 17224da | 2015-05-18 17:37:34 -0700 | [diff] [blame] | 83 | def host_could_be_in_afe(hostname): |
| 84 | """Check if the host could be in Autotest Front End. |
| 85 | |
| 86 | Report whether or not a host could be in AFE, without actually |
| 87 | consulting AFE. This method exists because some systems are in the |
| 88 | lab zone, but not actually managed by AFE. |
| 89 | |
| 90 | @param hostname: The hostname to check. |
| 91 | @returns True if hostname is in lab zone, and does not match *-dev-* |
| 92 | """ |
| 93 | # Do the 'dev' check first, so that we skip DNS lookup if the |
| 94 | # hostname matches. This should give us greater resilience to lab |
| 95 | # failures. |
| 96 | return (hostname.find('-dev-') == -1) and host_is_in_lab_zone(hostname) |
| 97 | |
| 98 | |
beeps | c4fb147 | 2013-05-08 21:49:48 -0700 | [diff] [blame] | 99 | def get_chrome_version(job_views): |
| 100 | """ |
| 101 | Retrieves the version of the chrome binary associated with a job. |
| 102 | |
| 103 | When a test runs we query the chrome binary for it's version and drop |
| 104 | that value into a client keyval. To retrieve the chrome version we get all |
| 105 | the views associated with a test from the db, including those of the |
| 106 | server and client jobs, and parse the version out of the first test view |
| 107 | that has it. If we never ran a single test in the suite the job_views |
| 108 | dictionary will not contain a chrome version. |
| 109 | |
| 110 | This method cannot retrieve the chrome version from a dictionary that |
| 111 | does not conform to the structure of an autotest tko view. |
| 112 | |
| 113 | @param job_views: a list of a job's result views, as returned by |
| 114 | the get_detailed_test_views method in rpc_interface. |
| 115 | @return: The chrome version string, or None if one can't be found. |
| 116 | """ |
| 117 | |
| 118 | # Aborted jobs have no views. |
| 119 | if not job_views: |
| 120 | return None |
| 121 | |
| 122 | for view in job_views: |
| 123 | if (view.get('attributes') |
| 124 | and constants.CHROME_VERSION in view['attributes'].keys()): |
| 125 | |
| 126 | return view['attributes'].get(constants.CHROME_VERSION) |
| 127 | |
| 128 | logging.warning('Could not find chrome version for failure.') |
| 129 | return None |
| 130 | |
| 131 | |
Simran Basi | dd12997 | 2014-09-11 14:34:49 -0700 | [diff] [blame] | 132 | def get_interface_mac_address(interface): |
| 133 | """Return the MAC address of a given interface. |
| 134 | |
| 135 | @param interface: Interface to look up the MAC address of. |
| 136 | """ |
| 137 | interface_link = base_utils.run( |
| 138 | 'ip addr show %s | grep link/ether' % interface).stdout |
| 139 | # The output will be in the format of: |
| 140 | # 'link/ether <mac> brd ff:ff:ff:ff:ff:ff' |
| 141 | return interface_link.split()[1] |
| 142 | |
| 143 | |
| 144 | def get_offload_gsuri(): |
| 145 | """Return the GSURI to offload test results to. |
| 146 | |
| 147 | For the normal use case this is the results_storage_server in the |
| 148 | global_config. |
| 149 | |
| 150 | However partners using Moblab will be offloading their results to a |
| 151 | subdirectory of their image storage buckets. The subdirectory is |
| 152 | determined by the MAC Address of the Moblab device. |
| 153 | |
| 154 | @returns gsuri to offload test results to. |
| 155 | """ |
Dan Shi | 60cf6a9 | 2015-01-29 17:22:49 -0800 | [diff] [blame] | 156 | if not lsbrelease_utils.is_moblab(): |
Simran Basi | dd12997 | 2014-09-11 14:34:49 -0700 | [diff] [blame] | 157 | return DEFAULT_OFFLOAD_GSURI |
| 158 | moblab_id_filepath = '/home/moblab/.moblab_id' |
| 159 | if os.path.exists(moblab_id_filepath): |
| 160 | with open(moblab_id_filepath, 'r') as moblab_id_file: |
| 161 | random_id = moblab_id_file.read() |
| 162 | else: |
| 163 | random_id = uuid.uuid1() |
| 164 | with open(moblab_id_filepath, 'w') as moblab_id_file: |
| 165 | moblab_id_file.write('%s' % random_id) |
| 166 | return '%sresults/%s/%s/' % ( |
| 167 | global_config.global_config.get_config_value( |
| 168 | 'CROS', 'image_storage_server'), |
| 169 | get_interface_mac_address(MOBLAB_ETH), random_id) |
| 170 | |
| 171 | |
Simran Basi | 87d7a21 | 2012-09-27 10:41:05 -0700 | [diff] [blame] | 172 | # TODO(petermayo): crosbug.com/31826 Share this with _GsUpload in |
| 173 | # //chromite.git/buildbot/prebuilt.py somewhere/somehow |
| 174 | def gs_upload(local_file, remote_file, acl, result_dir=None, |
| 175 | transfer_timeout=300, acl_timeout=300): |
| 176 | """Upload to GS bucket. |
| 177 | |
| 178 | @param local_file: Local file to upload |
| 179 | @param remote_file: Remote location to upload the local_file to. |
| 180 | @param acl: name or file used for controlling access to the uploaded |
| 181 | file. |
| 182 | @param result_dir: Result directory if you want to add tracing to the |
| 183 | upload. |
beeps | fda8f41 | 2013-05-02 19:08:20 -0700 | [diff] [blame] | 184 | @param transfer_timeout: Timeout for this upload call. |
| 185 | @param acl_timeout: Timeout for the acl call needed to confirm that |
| 186 | the uploader has permissions to execute the upload. |
Simran Basi | 87d7a21 | 2012-09-27 10:41:05 -0700 | [diff] [blame] | 187 | |
| 188 | @raise CmdError: the exit code of the gsutil call was not 0. |
| 189 | |
| 190 | @returns True/False - depending on if the upload succeeded or failed. |
| 191 | """ |
| 192 | # https://developers.google.com/storage/docs/accesscontrol#extension |
| 193 | CANNED_ACLS = ['project-private', 'private', 'public-read', |
| 194 | 'public-read-write', 'authenticated-read', |
| 195 | 'bucket-owner-read', 'bucket-owner-full-control'] |
| 196 | _GSUTIL_BIN = 'gsutil' |
| 197 | acl_cmd = None |
| 198 | if acl in CANNED_ACLS: |
| 199 | cmd = '%s cp -a %s %s %s' % (_GSUTIL_BIN, acl, local_file, remote_file) |
| 200 | else: |
| 201 | # For private uploads we assume that the overlay board is set up |
| 202 | # properly and a googlestore_acl.xml is present, if not this script |
| 203 | # errors |
| 204 | cmd = '%s cp -a private %s %s' % (_GSUTIL_BIN, local_file, remote_file) |
| 205 | if not os.path.exists(acl): |
| 206 | logging.error('Unable to find ACL File %s.', acl) |
| 207 | return False |
| 208 | acl_cmd = '%s setacl %s %s' % (_GSUTIL_BIN, acl, remote_file) |
| 209 | if not result_dir: |
| 210 | base_utils.run(cmd, timeout=transfer_timeout, verbose=True) |
| 211 | if acl_cmd: |
| 212 | base_utils.run(acl_cmd, timeout=acl_timeout, verbose=True) |
| 213 | return True |
| 214 | with open(os.path.join(result_dir, 'tracing'), 'w') as ftrace: |
| 215 | ftrace.write('Preamble\n') |
| 216 | base_utils.run(cmd, timeout=transfer_timeout, verbose=True, |
| 217 | stdout_tee=ftrace, stderr_tee=ftrace) |
| 218 | if acl_cmd: |
| 219 | ftrace.write('\nACL setting\n') |
| 220 | # Apply the passed in ACL xml file to the uploaded object. |
| 221 | base_utils.run(acl_cmd, timeout=acl_timeout, verbose=True, |
| 222 | stdout_tee=ftrace, stderr_tee=ftrace) |
| 223 | ftrace.write('Postamble\n') |
| 224 | return True |
Simran Basi | af9b8e7 | 2012-10-12 15:02:36 -0700 | [diff] [blame] | 225 | |
| 226 | |
Gilad Arnold | 0ed760c | 2012-11-05 23:42:53 -0800 | [diff] [blame] | 227 | def gs_ls(uri_pattern): |
| 228 | """Returns a list of URIs that match a given pattern. |
| 229 | |
| 230 | @param uri_pattern: a GS URI pattern, may contain wildcards |
| 231 | |
| 232 | @return A list of URIs matching the given pattern. |
| 233 | |
| 234 | @raise CmdError: the gsutil command failed. |
| 235 | |
| 236 | """ |
| 237 | gs_cmd = ' '.join(['gsutil', 'ls', uri_pattern]) |
| 238 | result = base_utils.system_output(gs_cmd).splitlines() |
| 239 | return [path.rstrip() for path in result if path] |
| 240 | |
| 241 | |
Simran Basi | af9b8e7 | 2012-10-12 15:02:36 -0700 | [diff] [blame] | 242 | def nuke_pids(pid_list, signal_queue=[signal.SIGTERM, signal.SIGKILL]): |
| 243 | """ |
| 244 | Given a list of pid's, kill them via an esclating series of signals. |
| 245 | |
| 246 | @param pid_list: List of PID's to kill. |
| 247 | @param signal_queue: Queue of signals to send the PID's to terminate them. |
Prashanth B | cf731e3 | 2014-08-10 18:03:57 -0700 | [diff] [blame] | 248 | |
| 249 | @return: A mapping of the signal name to the number of processes it |
| 250 | was sent to. |
Simran Basi | af9b8e7 | 2012-10-12 15:02:36 -0700 | [diff] [blame] | 251 | """ |
Prashanth B | cf731e3 | 2014-08-10 18:03:57 -0700 | [diff] [blame] | 252 | sig_count = {} |
| 253 | # Though this is slightly hacky it beats hardcoding names anyday. |
| 254 | sig_names = dict((k, v) for v, k in signal.__dict__.iteritems() |
| 255 | if v.startswith('SIG')) |
Simran Basi | af9b8e7 | 2012-10-12 15:02:36 -0700 | [diff] [blame] | 256 | for sig in signal_queue: |
| 257 | logging.debug('Sending signal %s to the following pids:', sig) |
Prashanth B | cf731e3 | 2014-08-10 18:03:57 -0700 | [diff] [blame] | 258 | sig_count[sig_names.get(sig, 'unknown_signal')] = len(pid_list) |
Simran Basi | af9b8e7 | 2012-10-12 15:02:36 -0700 | [diff] [blame] | 259 | for pid in pid_list: |
| 260 | logging.debug('Pid %d', pid) |
| 261 | try: |
| 262 | os.kill(pid, sig) |
| 263 | except OSError: |
| 264 | # The process may have died from a previous signal before we |
| 265 | # could kill it. |
| 266 | pass |
David James | f77198b | 2014-11-17 17:23:12 -0800 | [diff] [blame] | 267 | if sig == signal.SIGKILL: |
| 268 | return sig_count |
Prashanth B | cf731e3 | 2014-08-10 18:03:57 -0700 | [diff] [blame] | 269 | pid_list = [pid for pid in pid_list if base_utils.pid_is_alive(pid)] |
| 270 | if not pid_list: |
| 271 | break |
Simran Basi | af9b8e7 | 2012-10-12 15:02:36 -0700 | [diff] [blame] | 272 | time.sleep(CHECK_PID_IS_ALIVE_TIMEOUT) |
| 273 | failed_list = [] |
Simran Basi | af9b8e7 | 2012-10-12 15:02:36 -0700 | [diff] [blame] | 274 | for pid in pid_list: |
| 275 | if base_utils.pid_is_alive(pid): |
| 276 | failed_list.append('Could not kill %d for process name: %s.' % pid, |
Simran Basi | 6272320 | 2013-01-22 15:24:49 -0800 | [diff] [blame] | 277 | base_utils.get_process_name(pid)) |
Simran Basi | af9b8e7 | 2012-10-12 15:02:36 -0700 | [diff] [blame] | 278 | if failed_list: |
| 279 | raise error.AutoservRunError('Following errors occured: %s' % |
| 280 | failed_list, None) |
Prashanth B | cf731e3 | 2014-08-10 18:03:57 -0700 | [diff] [blame] | 281 | return sig_count |
Gilad Arnold | 0ed760c | 2012-11-05 23:42:53 -0800 | [diff] [blame] | 282 | |
| 283 | |
| 284 | def externalize_host(host): |
| 285 | """Returns an externally accessible host name. |
| 286 | |
| 287 | @param host: a host name or address (string) |
| 288 | |
| 289 | @return An externally visible host name or address |
| 290 | |
| 291 | """ |
| 292 | return socket.gethostname() if host in _LOCAL_HOST_LIST else host |
Simran Basi | 22aa9fe | 2012-12-07 16:37:09 -0800 | [diff] [blame] | 293 | |
| 294 | |
beeps | 60aec24 | 2013-06-26 14:47:48 -0700 | [diff] [blame] | 295 | def urlopen_socket_timeout(url, data=None, timeout=5): |
| 296 | """ |
| 297 | Wrapper to urllib2.urlopen with a socket timeout. |
| 298 | |
| 299 | This method will convert all socket timeouts to |
| 300 | TimeoutExceptions, so we can use it in conjunction |
| 301 | with the rpc retry decorator and continue to handle |
| 302 | other URLErrors as we see fit. |
| 303 | |
| 304 | @param url: The url to open. |
| 305 | @param data: The data to send to the url (eg: the urlencoded dictionary |
| 306 | used with a POST call). |
| 307 | @param timeout: The timeout for this urlopen call. |
| 308 | |
| 309 | @return: The response of the urlopen call. |
| 310 | |
| 311 | @raises: error.TimeoutException when a socket timeout occurs. |
Dan Shi | 6c00dde | 2013-07-29 17:47:29 -0700 | [diff] [blame] | 312 | urllib2.URLError for errors that not caused by timeout. |
| 313 | urllib2.HTTPError for errors like 404 url not found. |
beeps | 60aec24 | 2013-06-26 14:47:48 -0700 | [diff] [blame] | 314 | """ |
| 315 | old_timeout = socket.getdefaulttimeout() |
| 316 | socket.setdefaulttimeout(timeout) |
| 317 | try: |
| 318 | return urllib2.urlopen(url, data=data) |
| 319 | except urllib2.URLError as e: |
| 320 | if type(e.reason) is socket.timeout: |
| 321 | raise error.TimeoutException(str(e)) |
Dan Shi | 6c00dde | 2013-07-29 17:47:29 -0700 | [diff] [blame] | 322 | raise |
beeps | 60aec24 | 2013-06-26 14:47:48 -0700 | [diff] [blame] | 323 | finally: |
| 324 | socket.setdefaulttimeout(old_timeout) |
Luis Lozano | 40b7d0d | 2014-01-17 15:12:06 -0800 | [diff] [blame] | 325 | |
| 326 | |
| 327 | def parse_chrome_version(version_string): |
| 328 | """ |
| 329 | Parse a chrome version string and return version and milestone. |
| 330 | |
| 331 | Given a chrome version of the form "W.X.Y.Z", return "W.X.Y.Z" as |
| 332 | the version and "W" as the milestone. |
| 333 | |
| 334 | @param version_string: Chrome version string. |
| 335 | @return: a tuple (chrome_version, milestone). If the incoming version |
| 336 | string is not of the form "W.X.Y.Z", chrome_version will |
| 337 | be set to the incoming "version_string" argument and the |
| 338 | milestone will be set to the empty string. |
| 339 | """ |
| 340 | match = re.search('(\d+)\.\d+\.\d+\.\d+', version_string) |
| 341 | ver = match.group(0) if match else version_string |
| 342 | milestone = match.group(1) if match else '' |
| 343 | return ver, milestone |
beeps | bff9f9d | 2013-12-06 11:14:08 -0800 | [diff] [blame] | 344 | |
| 345 | |
Dan Shi | f6c65bd | 2014-08-29 16:15:07 -0700 | [diff] [blame] | 346 | def is_localhost(server): |
| 347 | """Check if server is equivalent to localhost. |
| 348 | |
| 349 | @param server: Name of the server to check. |
| 350 | |
| 351 | @return: True if given server is equivalent to localhost. |
Dan Shi | 767dced | 2015-02-01 00:21:07 -0800 | [diff] [blame] | 352 | |
Dan Shi | f6c65bd | 2014-08-29 16:15:07 -0700 | [diff] [blame] | 353 | @raise socket.gaierror: If server name failed to be resolved. |
| 354 | """ |
| 355 | if server in _LOCAL_HOST_LIST: |
| 356 | return True |
| 357 | try: |
| 358 | return (socket.gethostbyname(socket.gethostname()) == |
| 359 | socket.gethostbyname(server)) |
| 360 | except socket.gaierror: |
| 361 | logging.error('Failed to resolve server name %s.', server) |
| 362 | return False |
Dan Shi | 767dced | 2015-02-01 00:21:07 -0800 | [diff] [blame] | 363 | |
| 364 | |
MK Ryu | 8f8cdb4 | 2015-05-11 17:41:14 -0700 | [diff] [blame] | 365 | def is_puppylab_vm(server): |
| 366 | """Check if server is a virtual machine in puppylab. |
| 367 | |
| 368 | In the virtual machine testing environment (i.e., puppylab), each |
| 369 | shard VM has a hostname like localhost:<port>. |
| 370 | |
| 371 | @param server: Server name to check. |
| 372 | |
| 373 | @return True if given server is a virtual machine in puppylab. |
| 374 | |
| 375 | """ |
| 376 | # TODO(mkryu): This is a puppylab specific hack. Please update |
| 377 | # this method if you have a better solution. |
| 378 | regex = re.compile(r'(.+):\d+') |
| 379 | m = regex.match(server) |
| 380 | if m: |
| 381 | return m.group(1) in _LOCAL_HOST_LIST |
| 382 | return False |
| 383 | |
| 384 | |
Dan Shi | 767dced | 2015-02-01 00:21:07 -0800 | [diff] [blame] | 385 | def get_function_arg_value(func, arg_name, args, kwargs): |
| 386 | """Get the value of the given argument for the function. |
| 387 | |
| 388 | @param func: Function being called with given arguments. |
| 389 | @param arg_name: Name of the argument to look for value. |
| 390 | @param args: arguments for function to be called. |
| 391 | @param kwargs: keyword arguments for function to be called. |
| 392 | |
| 393 | @return: The value of the given argument for the function. |
| 394 | |
| 395 | @raise ValueError: If the argument is not listed function arguemnts. |
| 396 | @raise KeyError: If no value is found for the given argument. |
| 397 | """ |
| 398 | if arg_name in kwargs: |
| 399 | return kwargs[arg_name] |
| 400 | |
| 401 | argspec = inspect.getargspec(func) |
| 402 | index = argspec.args.index(arg_name) |
| 403 | try: |
| 404 | return args[index] |
| 405 | except IndexError: |
| 406 | try: |
| 407 | # The argument can use a default value. Reverse the default value |
| 408 | # so argument with default value can be counted from the last to |
| 409 | # the first. |
| 410 | return argspec.defaults[::-1][len(argspec.args) - index - 1] |
| 411 | except IndexError: |
| 412 | raise KeyError('Argument %s is not given a value. argspec: %s, ' |
| 413 | 'args:%s, kwargs:%s' % |
| 414 | (arg_name, argspec, args, kwargs)) |
Dan Shi | 549fb82 | 2015-03-24 18:01:11 -0700 | [diff] [blame] | 415 | |
| 416 | |
| 417 | def version_match(build_version, release_version, update_url=''): |
| 418 | """Compare release versino from lsb-release with cros-version label. |
| 419 | |
| 420 | build_version is a string based on build name. It is prefixed with builder |
| 421 | info and branch ID, e.g., lumpy-release/R43-6809.0.0. It may not include |
| 422 | builder info, e.g., lumpy-release, in which case, update_url shall be passed |
| 423 | in to determine if the build is a trybot or pgo-generate build. |
| 424 | release_version is retrieved from lsb-release. |
| 425 | These two values might not match exactly. |
| 426 | |
| 427 | The method is designed to compare version for following 6 scenarios with |
| 428 | samples of build version and expected release version: |
Rebecca Silberstein | b2d204b | 2015-08-04 10:45:50 -0700 | [diff] [blame] | 429 | 1. trybot non-release build (paladin, pre-cq or test-ap build). |
Dan Shi | 549fb82 | 2015-03-24 18:01:11 -0700 | [diff] [blame] | 430 | build version: trybot-lumpy-paladin/R27-3837.0.0-b123 |
| 431 | release version: 3837.0.2013_03_21_1340 |
| 432 | |
| 433 | 2. trybot release build. |
| 434 | build version: trybot-lumpy-release/R27-3837.0.0-b456 |
| 435 | release version: 3837.0.0 |
| 436 | |
| 437 | 3. buildbot official release build. |
| 438 | build version: lumpy-release/R27-3837.0.0 |
| 439 | release version: 3837.0.0 |
| 440 | |
| 441 | 4. non-official paladin rc build. |
| 442 | build version: lumpy-paladin/R27-3878.0.0-rc7 |
| 443 | release version: 3837.0.0-rc7 |
| 444 | |
| 445 | 5. chrome-perf build. |
| 446 | build version: lumpy-chrome-perf/R28-3837.0.0-b2996 |
| 447 | release version: 3837.0.0 |
| 448 | |
| 449 | 6. pgo-generate build. |
| 450 | build version: lumpy-release-pgo-generate/R28-3837.0.0-b2996 |
| 451 | release version: 3837.0.0-pgo-generate |
| 452 | |
| 453 | TODO: This logic has a bug if a trybot paladin build failed to be |
| 454 | installed in a DUT running an older trybot paladin build with same |
| 455 | platform number, but different build number (-b###). So to conclusively |
| 456 | determine if a tryjob paladin build is imaged successfully, we may need |
| 457 | to find out the date string from update url. |
| 458 | |
| 459 | @param build_version: Build name for cros version, e.g. |
| 460 | peppy-release/R43-6809.0.0 or R43-6809.0.0 |
| 461 | @param release_version: Release version retrieved from lsb-release, |
| 462 | e.g., 6809.0.0 |
| 463 | @param update_url: Update url which include the full builder information. |
| 464 | Default is set to empty string. |
| 465 | |
| 466 | @return: True if the values match, otherwise returns False. |
| 467 | """ |
| 468 | # If the build is from release, CQ or PFQ builder, cros-version label must |
| 469 | # be ended with release version in lsb-release. |
| 470 | if build_version.endswith(release_version): |
| 471 | return True |
| 472 | |
| 473 | # Remove R#- and -b# at the end of build version |
| 474 | stripped_version = re.sub(r'(R\d+-|-b\d+)', '', build_version) |
| 475 | # Trim the builder info, e.g., trybot-lumpy-paladin/ |
| 476 | stripped_version = stripped_version.split('/')[-1] |
| 477 | |
Dan Shi | e24761c | 2015-07-06 15:23:03 -0700 | [diff] [blame] | 478 | is_trybot_non_release_build = ( |
Rebecca Silberstein | b2d204b | 2015-08-04 10:45:50 -0700 | [diff] [blame] | 479 | re.match(r'.*trybot-.+-(paladin|pre-cq|test-ap)', build_version) or |
| 480 | re.match(r'.*trybot-.+-(paladin|pre-cq|test-ap)', update_url)) |
Dan Shi | 549fb82 | 2015-03-24 18:01:11 -0700 | [diff] [blame] | 481 | |
| 482 | # Replace date string with 0 in release_version |
| 483 | release_version_no_date = re.sub(r'\d{4}_\d{2}_\d{2}_\d+', '0', |
| 484 | release_version) |
| 485 | has_date_string = release_version != release_version_no_date |
| 486 | |
| 487 | is_pgo_generate_build = ( |
| 488 | re.match(r'.+-pgo-generate', build_version) or |
| 489 | re.match(r'.+-pgo-generate', update_url)) |
| 490 | |
| 491 | # Remove |-pgo-generate| in release_version |
| 492 | release_version_no_pgo = release_version.replace('-pgo-generate', '') |
| 493 | has_pgo_generate = release_version != release_version_no_pgo |
| 494 | |
Dan Shi | e24761c | 2015-07-06 15:23:03 -0700 | [diff] [blame] | 495 | if is_trybot_non_release_build: |
Dan Shi | 549fb82 | 2015-03-24 18:01:11 -0700 | [diff] [blame] | 496 | if not has_date_string: |
Dan Shi | e24761c | 2015-07-06 15:23:03 -0700 | [diff] [blame] | 497 | logging.error('A trybot paladin or pre-cq build is expected. ' |
| 498 | 'Version "%s" is not a paladin or pre-cq build.', |
| 499 | release_version) |
Dan Shi | 549fb82 | 2015-03-24 18:01:11 -0700 | [diff] [blame] | 500 | return False |
| 501 | return stripped_version == release_version_no_date |
| 502 | elif is_pgo_generate_build: |
| 503 | if not has_pgo_generate: |
| 504 | logging.error('A pgo-generate build is expected. Version ' |
| 505 | '"%s" is not a pgo-generate build.', |
| 506 | release_version) |
| 507 | return False |
| 508 | return stripped_version == release_version_no_pgo |
| 509 | else: |
| 510 | if has_date_string: |
Dan Shi | e24761c | 2015-07-06 15:23:03 -0700 | [diff] [blame] | 511 | logging.error('Unexpected date found in a non trybot paladin or ' |
| 512 | 'pre-cq build.') |
Dan Shi | 549fb82 | 2015-03-24 18:01:11 -0700 | [diff] [blame] | 513 | return False |
| 514 | # Versioned build, i.e., rc or release build. |
| 515 | return stripped_version == release_version |
Dan Shi | 7836d25 | 2015-04-27 15:33:58 -0700 | [diff] [blame] | 516 | |
| 517 | |
| 518 | def get_real_user(): |
| 519 | """Get the real user that runs the script. |
| 520 | |
| 521 | The function check environment variable SUDO_USER for the user if the |
| 522 | script is run with sudo. Otherwise, it returns the value of environment |
| 523 | variable USER. |
| 524 | |
| 525 | @return: The user name that runs the script. |
| 526 | |
| 527 | """ |
| 528 | user = os.environ.get('SUDO_USER') |
| 529 | if not user: |
| 530 | user = os.environ.get('USER') |
| 531 | return user |
Dan Shi | ca3be48 | 2015-05-05 23:23:53 -0700 | [diff] [blame] | 532 | |
| 533 | |
| 534 | def sudo_require_password(): |
| 535 | """Test if the process can run sudo command without using password. |
| 536 | |
| 537 | @return: True if the process needs password to run sudo command. |
| 538 | |
| 539 | """ |
| 540 | try: |
| 541 | base_utils.run('sudo -n true') |
| 542 | return False |
| 543 | except error.CmdError: |
| 544 | logging.warn('sudo command requires password.') |
| 545 | return True |
Dan Shi | ff78f11 | 2015-06-12 13:34:02 -0700 | [diff] [blame] | 546 | |
| 547 | |
| 548 | def is_in_container(): |
| 549 | """Check if the process is running inside a container. |
| 550 | |
| 551 | @return: True if the process is running inside a container, otherwise False. |
| 552 | """ |
J. Richard Barnette | 8fbb4d1 | 2015-06-26 12:51:26 -0700 | [diff] [blame] | 553 | result = base_utils.run('grep -q "/lxc/" /proc/1/cgroup', |
| 554 | verbose=False, ignore_status=True) |
| 555 | return result.exit_status == 0 |
Ilja H. Friedel | 5b35656 | 2015-06-26 11:51:10 -0700 | [diff] [blame] | 556 | |
| 557 | |
| 558 | def is_flash_installed(): |
| 559 | """ |
| 560 | The Adobe Flash binary is only distributed with internal builds. |
| 561 | """ |
| 562 | return (os.path.exists('/opt/google/chrome/pepper/libpepflashplayer.so') |
| 563 | and os.path.exists('/opt/google/chrome/pepper/pepper-flash.info')) |
| 564 | |
| 565 | |
| 566 | def verify_flash_installed(): |
| 567 | """ |
| 568 | The Adobe Flash binary is only distributed with internal builds. |
| 569 | Warn users of public builds of the extra dependency. |
| 570 | """ |
| 571 | if not is_flash_installed(): |
| 572 | raise error.TestNAError('No Adobe Flash binary installed.') |