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. 4 5import inspect 6import logging 7import os 8import re 9import signal 10import socket 11import struct 12import time 13import urllib2 14import uuid 15import wave 16 17from autotest_lib.client.common_lib import base_utils 18from autotest_lib.client.common_lib import error 19from autotest_lib.client.common_lib import global_config 20from autotest_lib.client.common_lib import lsbrelease_utils 21from autotest_lib.client.cros import constants 22 23 24CONFIG = global_config.global_config 25 26# Keep checking if the pid is alive every second until the timeout (in seconds) 27CHECK_PID_IS_ALIVE_TIMEOUT = 6 28 29_LOCAL_HOST_LIST = ('localhost', '127.0.0.1') 30 31# The default address of a vm gateway. 32DEFAULT_VM_GATEWAY = '10.0.2.2' 33 34# Google Storage bucket URI to store results in. 35DEFAULT_OFFLOAD_GSURI = CONFIG.get_config_value( 36 'CROS', 'results_storage_server', default=None) 37 38# Default Moblab Ethernet Interface. 39MOBLAB_ETH = 'eth0' 40 41# A list of subnets that requires dedicated devserver and drone in the same 42# subnet. Each item is a tuple of (subnet_ip, mask_bits), e.g., 43# ('192.168.0.0', 24)) 44RESTRICTED_SUBNETS = [] 45restricted_subnets_list = CONFIG.get_config_value( 46 'CROS', 'restricted_subnets', type=list, default=[]) 47for subnet in restricted_subnets_list: 48 ip, mask_bits = subnet.split(':') 49 RESTRICTED_SUBNETS.append((ip, int(mask_bits))) 50 51# regex pattern for CLIENT/wireless_ssid_ config. For example, global config 52# can have following config in CLIENT section to indicate that hosts in subnet 53# 192.168.0.1/24 should use wireless ssid of `ssid_1` 54# wireless_ssid_192.168.0.1/24: ssid_1 55WIRELESS_SSID_PATTERN = 'wireless_ssid_(.*)/(\d+)' 56 57def ping(host, deadline=None, tries=None, timeout=60): 58 """Attempt to ping |host|. 59 60 Shell out to 'ping' to try to reach |host| for |timeout| seconds. 61 Returns exit code of ping. 62 63 Per 'man ping', if you specify BOTH |deadline| and |tries|, ping only 64 returns 0 if we get responses to |tries| pings within |deadline| seconds. 65 66 Specifying |deadline| or |count| alone should return 0 as long as 67 some packets receive responses. 68 69 @param host: the host to ping. 70 @param deadline: seconds within which |tries| pings must succeed. 71 @param tries: number of pings to send. 72 @param timeout: number of seconds after which to kill 'ping' command. 73 @return exit code of ping command. 74 """ 75 args = [host] 76 if deadline: 77 args.append('-w%d' % deadline) 78 if tries: 79 args.append('-c%d' % tries) 80 return base_utils.run('ping', args=args, 81 ignore_status=True, timeout=timeout, 82 stdout_tee=base_utils.TEE_TO_LOGS, 83 stderr_tee=base_utils.TEE_TO_LOGS).exit_status 84 85 86def host_is_in_lab_zone(hostname): 87 """Check if the host is in the CLIENT.dns_zone. 88 89 @param hostname: The hostname to check. 90 @returns True if hostname.dns_zone resolves, otherwise False. 91 """ 92 host_parts = hostname.split('.') 93 dns_zone = CONFIG.get_config_value('CLIENT', 'dns_zone', default=None) 94 fqdn = '%s.%s' % (host_parts[0], dns_zone) 95 try: 96 socket.gethostbyname(fqdn) 97 return True 98 except socket.gaierror: 99 return False 100 101 102def host_could_be_in_afe(hostname): 103 """Check if the host could be in Autotest Front End. 104 105 Report whether or not a host could be in AFE, without actually 106 consulting AFE. This method exists because some systems are in the 107 lab zone, but not actually managed by AFE. 108 109 @param hostname: The hostname to check. 110 @returns True if hostname is in lab zone, and does not match *-dev-* 111 """ 112 # Do the 'dev' check first, so that we skip DNS lookup if the 113 # hostname matches. This should give us greater resilience to lab 114 # failures. 115 return (hostname.find('-dev-') == -1) and host_is_in_lab_zone(hostname) 116 117 118def get_chrome_version(job_views): 119 """ 120 Retrieves the version of the chrome binary associated with a job. 121 122 When a test runs we query the chrome binary for it's version and drop 123 that value into a client keyval. To retrieve the chrome version we get all 124 the views associated with a test from the db, including those of the 125 server and client jobs, and parse the version out of the first test view 126 that has it. If we never ran a single test in the suite the job_views 127 dictionary will not contain a chrome version. 128 129 This method cannot retrieve the chrome version from a dictionary that 130 does not conform to the structure of an autotest tko view. 131 132 @param job_views: a list of a job's result views, as returned by 133 the get_detailed_test_views method in rpc_interface. 134 @return: The chrome version string, or None if one can't be found. 135 """ 136 137 # Aborted jobs have no views. 138 if not job_views: 139 return None 140 141 for view in job_views: 142 if (view.get('attributes') 143 and constants.CHROME_VERSION in view['attributes'].keys()): 144 145 return view['attributes'].get(constants.CHROME_VERSION) 146 147 logging.warning('Could not find chrome version for failure.') 148 return None 149 150 151def get_interface_mac_address(interface): 152 """Return the MAC address of a given interface. 153 154 @param interface: Interface to look up the MAC address of. 155 """ 156 interface_link = base_utils.run( 157 'ip addr show %s | grep link/ether' % interface).stdout 158 # The output will be in the format of: 159 # 'link/ether <mac> brd ff:ff:ff:ff:ff:ff' 160 return interface_link.split()[1] 161 162 163def get_offload_gsuri(): 164 """Return the GSURI to offload test results to. 165 166 For the normal use case this is the results_storage_server in the 167 global_config. 168 169 However partners using Moblab will be offloading their results to a 170 subdirectory of their image storage buckets. The subdirectory is 171 determined by the MAC Address of the Moblab device. 172 173 @returns gsuri to offload test results to. 174 """ 175 if not lsbrelease_utils.is_moblab(): 176 return DEFAULT_OFFLOAD_GSURI 177 moblab_id_filepath = '/home/moblab/.moblab_id' 178 if os.path.exists(moblab_id_filepath): 179 with open(moblab_id_filepath, 'r') as moblab_id_file: 180 random_id = moblab_id_file.read() 181 else: 182 random_id = uuid.uuid1() 183 with open(moblab_id_filepath, 'w') as moblab_id_file: 184 moblab_id_file.write('%s' % random_id) 185 return '%sresults/%s/%s/' % ( 186 CONFIG.get_config_value('CROS', 'image_storage_server'), 187 get_interface_mac_address(MOBLAB_ETH), random_id) 188 189 190# TODO(petermayo): crosbug.com/31826 Share this with _GsUpload in 191# //chromite.git/buildbot/prebuilt.py somewhere/somehow 192def gs_upload(local_file, remote_file, acl, result_dir=None, 193 transfer_timeout=300, acl_timeout=300): 194 """Upload to GS bucket. 195 196 @param local_file: Local file to upload 197 @param remote_file: Remote location to upload the local_file to. 198 @param acl: name or file used for controlling access to the uploaded 199 file. 200 @param result_dir: Result directory if you want to add tracing to the 201 upload. 202 @param transfer_timeout: Timeout for this upload call. 203 @param acl_timeout: Timeout for the acl call needed to confirm that 204 the uploader has permissions to execute the upload. 205 206 @raise CmdError: the exit code of the gsutil call was not 0. 207 208 @returns True/False - depending on if the upload succeeded or failed. 209 """ 210 # https://developers.google.com/storage/docs/accesscontrol#extension 211 CANNED_ACLS = ['project-private', 'private', 'public-read', 212 'public-read-write', 'authenticated-read', 213 'bucket-owner-read', 'bucket-owner-full-control'] 214 _GSUTIL_BIN = 'gsutil' 215 acl_cmd = None 216 if acl in CANNED_ACLS: 217 cmd = '%s cp -a %s %s %s' % (_GSUTIL_BIN, acl, local_file, remote_file) 218 else: 219 # For private uploads we assume that the overlay board is set up 220 # properly and a googlestore_acl.xml is present, if not this script 221 # errors 222 cmd = '%s cp -a private %s %s' % (_GSUTIL_BIN, local_file, remote_file) 223 if not os.path.exists(acl): 224 logging.error('Unable to find ACL File %s.', acl) 225 return False 226 acl_cmd = '%s setacl %s %s' % (_GSUTIL_BIN, acl, remote_file) 227 if not result_dir: 228 base_utils.run(cmd, timeout=transfer_timeout, verbose=True) 229 if acl_cmd: 230 base_utils.run(acl_cmd, timeout=acl_timeout, verbose=True) 231 return True 232 with open(os.path.join(result_dir, 'tracing'), 'w') as ftrace: 233 ftrace.write('Preamble\n') 234 base_utils.run(cmd, timeout=transfer_timeout, verbose=True, 235 stdout_tee=ftrace, stderr_tee=ftrace) 236 if acl_cmd: 237 ftrace.write('\nACL setting\n') 238 # Apply the passed in ACL xml file to the uploaded object. 239 base_utils.run(acl_cmd, timeout=acl_timeout, verbose=True, 240 stdout_tee=ftrace, stderr_tee=ftrace) 241 ftrace.write('Postamble\n') 242 return True 243 244 245def gs_ls(uri_pattern): 246 """Returns a list of URIs that match a given pattern. 247 248 @param uri_pattern: a GS URI pattern, may contain wildcards 249 250 @return A list of URIs matching the given pattern. 251 252 @raise CmdError: the gsutil command failed. 253 254 """ 255 gs_cmd = ' '.join(['gsutil', 'ls', uri_pattern]) 256 result = base_utils.system_output(gs_cmd).splitlines() 257 return [path.rstrip() for path in result if path] 258 259 260def nuke_pids(pid_list, signal_queue=[signal.SIGTERM, signal.SIGKILL]): 261 """ 262 Given a list of pid's, kill them via an esclating series of signals. 263 264 @param pid_list: List of PID's to kill. 265 @param signal_queue: Queue of signals to send the PID's to terminate them. 266 267 @return: A mapping of the signal name to the number of processes it 268 was sent to. 269 """ 270 sig_count = {} 271 # Though this is slightly hacky it beats hardcoding names anyday. 272 sig_names = dict((k, v) for v, k in signal.__dict__.iteritems() 273 if v.startswith('SIG')) 274 for sig in signal_queue: 275 logging.debug('Sending signal %s to the following pids:', sig) 276 sig_count[sig_names.get(sig, 'unknown_signal')] = len(pid_list) 277 for pid in pid_list: 278 logging.debug('Pid %d', pid) 279 try: 280 os.kill(pid, sig) 281 except OSError: 282 # The process may have died from a previous signal before we 283 # could kill it. 284 pass 285 if sig == signal.SIGKILL: 286 return sig_count 287 pid_list = [pid for pid in pid_list if base_utils.pid_is_alive(pid)] 288 if not pid_list: 289 break 290 time.sleep(CHECK_PID_IS_ALIVE_TIMEOUT) 291 failed_list = [] 292 for pid in pid_list: 293 if base_utils.pid_is_alive(pid): 294 failed_list.append('Could not kill %d for process name: %s.' % pid, 295 base_utils.get_process_name(pid)) 296 if failed_list: 297 raise error.AutoservRunError('Following errors occured: %s' % 298 failed_list, None) 299 return sig_count 300 301 302def externalize_host(host): 303 """Returns an externally accessible host name. 304 305 @param host: a host name or address (string) 306 307 @return An externally visible host name or address 308 309 """ 310 return socket.gethostname() if host in _LOCAL_HOST_LIST else host 311 312 313def urlopen_socket_timeout(url, data=None, timeout=5): 314 """ 315 Wrapper to urllib2.urlopen with a socket timeout. 316 317 This method will convert all socket timeouts to 318 TimeoutExceptions, so we can use it in conjunction 319 with the rpc retry decorator and continue to handle 320 other URLErrors as we see fit. 321 322 @param url: The url to open. 323 @param data: The data to send to the url (eg: the urlencoded dictionary 324 used with a POST call). 325 @param timeout: The timeout for this urlopen call. 326 327 @return: The response of the urlopen call. 328 329 @raises: error.TimeoutException when a socket timeout occurs. 330 urllib2.URLError for errors that not caused by timeout. 331 urllib2.HTTPError for errors like 404 url not found. 332 """ 333 old_timeout = socket.getdefaulttimeout() 334 socket.setdefaulttimeout(timeout) 335 try: 336 return urllib2.urlopen(url, data=data) 337 except urllib2.URLError as e: 338 if type(e.reason) is socket.timeout: 339 raise error.TimeoutException(str(e)) 340 raise 341 finally: 342 socket.setdefaulttimeout(old_timeout) 343 344 345def parse_chrome_version(version_string): 346 """ 347 Parse a chrome version string and return version and milestone. 348 349 Given a chrome version of the form "W.X.Y.Z", return "W.X.Y.Z" as 350 the version and "W" as the milestone. 351 352 @param version_string: Chrome version string. 353 @return: a tuple (chrome_version, milestone). If the incoming version 354 string is not of the form "W.X.Y.Z", chrome_version will 355 be set to the incoming "version_string" argument and the 356 milestone will be set to the empty string. 357 """ 358 match = re.search('(\d+)\.\d+\.\d+\.\d+', version_string) 359 ver = match.group(0) if match else version_string 360 milestone = match.group(1) if match else '' 361 return ver, milestone 362 363 364def is_localhost(server): 365 """Check if server is equivalent to localhost. 366 367 @param server: Name of the server to check. 368 369 @return: True if given server is equivalent to localhost. 370 371 @raise socket.gaierror: If server name failed to be resolved. 372 """ 373 if server in _LOCAL_HOST_LIST: 374 return True 375 try: 376 return (socket.gethostbyname(socket.gethostname()) == 377 socket.gethostbyname(server)) 378 except socket.gaierror: 379 logging.error('Failed to resolve server name %s.', server) 380 return False 381 382 383def is_puppylab_vm(server): 384 """Check if server is a virtual machine in puppylab. 385 386 In the virtual machine testing environment (i.e., puppylab), each 387 shard VM has a hostname like localhost:<port>. 388 389 @param server: Server name to check. 390 391 @return True if given server is a virtual machine in puppylab. 392 393 """ 394 # TODO(mkryu): This is a puppylab specific hack. Please update 395 # this method if you have a better solution. 396 regex = re.compile(r'(.+):\d+') 397 m = regex.match(server) 398 if m: 399 return m.group(1) in _LOCAL_HOST_LIST 400 return False 401 402 403def get_function_arg_value(func, arg_name, args, kwargs): 404 """Get the value of the given argument for the function. 405 406 @param func: Function being called with given arguments. 407 @param arg_name: Name of the argument to look for value. 408 @param args: arguments for function to be called. 409 @param kwargs: keyword arguments for function to be called. 410 411 @return: The value of the given argument for the function. 412 413 @raise ValueError: If the argument is not listed function arguemnts. 414 @raise KeyError: If no value is found for the given argument. 415 """ 416 if arg_name in kwargs: 417 return kwargs[arg_name] 418 419 argspec = inspect.getargspec(func) 420 index = argspec.args.index(arg_name) 421 try: 422 return args[index] 423 except IndexError: 424 try: 425 # The argument can use a default value. Reverse the default value 426 # so argument with default value can be counted from the last to 427 # the first. 428 return argspec.defaults[::-1][len(argspec.args) - index - 1] 429 except IndexError: 430 raise KeyError('Argument %s is not given a value. argspec: %s, ' 431 'args:%s, kwargs:%s' % 432 (arg_name, argspec, args, kwargs)) 433 434 435def version_match(build_version, release_version, update_url=''): 436 """Compare release versino from lsb-release with cros-version label. 437 438 build_version is a string based on build name. It is prefixed with builder 439 info and branch ID, e.g., lumpy-release/R43-6809.0.0. It may not include 440 builder info, e.g., lumpy-release, in which case, update_url shall be passed 441 in to determine if the build is a trybot or pgo-generate build. 442 release_version is retrieved from lsb-release. 443 These two values might not match exactly. 444 445 The method is designed to compare version for following 6 scenarios with 446 samples of build version and expected release version: 447 1. trybot non-release build (paladin, pre-cq or test-ap build). 448 build version: trybot-lumpy-paladin/R27-3837.0.0-b123 449 release version: 3837.0.2013_03_21_1340 450 451 2. trybot release build. 452 build version: trybot-lumpy-release/R27-3837.0.0-b456 453 release version: 3837.0.0 454 455 3. buildbot official release build. 456 build version: lumpy-release/R27-3837.0.0 457 release version: 3837.0.0 458 459 4. non-official paladin rc build. 460 build version: lumpy-paladin/R27-3878.0.0-rc7 461 release version: 3837.0.0-rc7 462 463 5. chrome-perf build. 464 build version: lumpy-chrome-perf/R28-3837.0.0-b2996 465 release version: 3837.0.0 466 467 6. pgo-generate build. 468 build version: lumpy-release-pgo-generate/R28-3837.0.0-b2996 469 release version: 3837.0.0-pgo-generate 470 471 TODO: This logic has a bug if a trybot paladin build failed to be 472 installed in a DUT running an older trybot paladin build with same 473 platform number, but different build number (-b###). So to conclusively 474 determine if a tryjob paladin build is imaged successfully, we may need 475 to find out the date string from update url. 476 477 @param build_version: Build name for cros version, e.g. 478 peppy-release/R43-6809.0.0 or R43-6809.0.0 479 @param release_version: Release version retrieved from lsb-release, 480 e.g., 6809.0.0 481 @param update_url: Update url which include the full builder information. 482 Default is set to empty string. 483 484 @return: True if the values match, otherwise returns False. 485 """ 486 # If the build is from release, CQ or PFQ builder, cros-version label must 487 # be ended with release version in lsb-release. 488 if build_version.endswith(release_version): 489 return True 490 491 # Remove R#- and -b# at the end of build version 492 stripped_version = re.sub(r'(R\d+-|-b\d+)', '', build_version) 493 # Trim the builder info, e.g., trybot-lumpy-paladin/ 494 stripped_version = stripped_version.split('/')[-1] 495 496 is_trybot_non_release_build = ( 497 re.match(r'.*trybot-.+-(paladin|pre-cq|test-ap)', build_version) or 498 re.match(r'.*trybot-.+-(paladin|pre-cq|test-ap)', update_url)) 499 500 # Replace date string with 0 in release_version 501 release_version_no_date = re.sub(r'\d{4}_\d{2}_\d{2}_\d+', '0', 502 release_version) 503 has_date_string = release_version != release_version_no_date 504 505 is_pgo_generate_build = ( 506 re.match(r'.+-pgo-generate', build_version) or 507 re.match(r'.+-pgo-generate', update_url)) 508 509 # Remove |-pgo-generate| in release_version 510 release_version_no_pgo = release_version.replace('-pgo-generate', '') 511 has_pgo_generate = release_version != release_version_no_pgo 512 513 if is_trybot_non_release_build: 514 if not has_date_string: 515 logging.error('A trybot paladin or pre-cq build is expected. ' 516 'Version "%s" is not a paladin or pre-cq build.', 517 release_version) 518 return False 519 return stripped_version == release_version_no_date 520 elif is_pgo_generate_build: 521 if not has_pgo_generate: 522 logging.error('A pgo-generate build is expected. Version ' 523 '"%s" is not a pgo-generate build.', 524 release_version) 525 return False 526 return stripped_version == release_version_no_pgo 527 else: 528 if has_date_string: 529 logging.error('Unexpected date found in a non trybot paladin or ' 530 'pre-cq build.') 531 return False 532 # Versioned build, i.e., rc or release build. 533 return stripped_version == release_version 534 535 536def get_real_user(): 537 """Get the real user that runs the script. 538 539 The function check environment variable SUDO_USER for the user if the 540 script is run with sudo. Otherwise, it returns the value of environment 541 variable USER. 542 543 @return: The user name that runs the script. 544 545 """ 546 user = os.environ.get('SUDO_USER') 547 if not user: 548 user = os.environ.get('USER') 549 return user 550 551 552def sudo_require_password(): 553 """Test if the process can run sudo command without using password. 554 555 @return: True if the process needs password to run sudo command. 556 557 """ 558 try: 559 base_utils.run('sudo -n true') 560 return False 561 except error.CmdError: 562 logging.warn('sudo command requires password.') 563 return True 564 565 566def is_in_container(): 567 """Check if the process is running inside a container. 568 569 @return: True if the process is running inside a container, otherwise False. 570 """ 571 result = base_utils.run('grep -q "/lxc/" /proc/1/cgroup', 572 verbose=False, ignore_status=True) 573 return result.exit_status == 0 574 575 576def is_flash_installed(): 577 """ 578 The Adobe Flash binary is only distributed with internal builds. 579 """ 580 return (os.path.exists('/opt/google/chrome/pepper/libpepflashplayer.so') 581 and os.path.exists('/opt/google/chrome/pepper/pepper-flash.info')) 582 583 584def verify_flash_installed(): 585 """ 586 The Adobe Flash binary is only distributed with internal builds. 587 Warn users of public builds of the extra dependency. 588 """ 589 if not is_flash_installed(): 590 raise error.TestNAError('No Adobe Flash binary installed.') 591 592 593def is_in_same_subnet(ip_1, ip_2, mask_bits=24): 594 """Check if two IP addresses are in the same subnet with given mask bits. 595 596 The two IP addresses are string of IPv4, e.g., '192.168.0.3'. 597 598 @param ip_1: First IP address to compare. 599 @param ip_2: Second IP address to compare. 600 @param mask_bits: Number of mask bits for subnet comparison. Default to 24. 601 602 @return: True if the two IP addresses are in the same subnet. 603 604 """ 605 mask = ((2L<<mask_bits-1) -1)<<(32-mask_bits) 606 ip_1_num = struct.unpack('!I', socket.inet_aton(ip_1))[0] 607 ip_2_num = struct.unpack('!I', socket.inet_aton(ip_2))[0] 608 return ip_1_num & mask == ip_2_num & mask 609 610 611def get_ip_address(hostname): 612 """Get the IP address of given hostname. 613 614 @param hostname: Hostname of a DUT. 615 616 @return: The IP address of given hostname. None if failed to resolve 617 hostname. 618 """ 619 try: 620 if hostname: 621 return socket.gethostbyname(hostname) 622 except socket.gaierror as e: 623 logging.error('Failed to get IP address of %s, error: %s.', hostname, e) 624 625 626def get_servers_in_same_subnet(host_ip, mask_bits, servers=None, 627 server_ip_map=None): 628 """Get the servers in the same subnet of the given host ip. 629 630 @param host_ip: The IP address of a dut to look for devserver. 631 @param mask_bits: Number of mask bits. 632 @param servers: A list of servers to be filtered by subnet specified by 633 host_ip and mask_bits. 634 @param server_ip_map: A map between the server name and its IP address. 635 The map can be pre-built for better performance, e.g., when 636 allocating a drone for an agent task. 637 638 @return: A list of servers in the same subnet of the given host ip. 639 640 """ 641 matched_servers = [] 642 if not servers and not server_ip_map: 643 raise ValueError('Either `servers` or `server_ip_map` must be given.') 644 if not servers: 645 servers = server_ip_map.keys() 646 for server in servers: 647 server_ip = server_ip_map.get(server) or get_ip_address(server) 648 if server_ip and is_in_same_subnet(server_ip, host_ip, mask_bits): 649 matched_servers.append(server) 650 return matched_servers 651 652 653def get_restricted_subnet(hostname, restricted_subnets=RESTRICTED_SUBNETS): 654 """Get the restricted subnet of given hostname. 655 656 @param hostname: Name of the host to look for matched restricted subnet. 657 @param restricted_subnets: A list of restricted subnets, default is set to 658 RESTRICTED_SUBNETS. 659 660 @return: A tuple of (subnet_ip, mask_bits), which defines a restricted 661 subnet. 662 """ 663 host_ip = get_ip_address(hostname) 664 if not host_ip: 665 return 666 for subnet_ip, mask_bits in restricted_subnets: 667 if is_in_same_subnet(subnet_ip, host_ip, mask_bits): 668 return subnet_ip, mask_bits 669 670 671def get_wireless_ssid(hostname): 672 """Get the wireless ssid based on given hostname. 673 674 The method tries to locate the wireless ssid in the same subnet of given 675 hostname first. If none is found, it returns the default setting in 676 CLIENT/wireless_ssid. 677 678 @param hostname: Hostname of the test device. 679 680 @return: wireless ssid for the test device. 681 """ 682 default_ssid = CONFIG.get_config_value('CLIENT', 'wireless_ssid', 683 default=None) 684 host_ip = get_ip_address(hostname) 685 if not host_ip: 686 return default_ssid 687 688 # Get all wireless ssid in the global config. 689 ssids = CONFIG.get_config_value_regex('CLIENT', WIRELESS_SSID_PATTERN) 690 691 for key, value in ssids.items(): 692 # The config key filtered by regex WIRELESS_SSID_PATTERN has a format of 693 # wireless_ssid_[subnet_ip]/[maskbit], for example: 694 # wireless_ssid_192.168.0.1/24 695 # Following line extract the subnet ip and mask bit from the key name. 696 match = re.match(WIRELESS_SSID_PATTERN, key) 697 subnet_ip, maskbit = match.groups() 698 if is_in_same_subnet(subnet_ip, host_ip, int(maskbit)): 699 return value 700 return default_ssid 701 702 703def parse_android_build(build_name): 704 """Get branch, target, build_id from the given build_name. 705 706 @param build_name: Name of an Android build, should be formated as 707 branch/target/build_id 708 709 @return: Tuple of branch, target, build_id 710 @raise ValueError: If the build_name is not correctly formated. 711 """ 712 branch, target, build_id = build_name.split('/') 713 return branch, target, build_id 714 715 716def extract_wav_frames(wave_file): 717 """Extract all frames from a WAV file. 718 719 wave_file: A Wave_read object representing a WAV file opened for reading. 720 721 @return: A list containing the frames in the WAV file. 722 """ 723 num_frames = wave_file.getnframes() 724 sample_width = wave_file.getsampwidth() 725 if sample_width == 1: 726 fmt = '%iB' # Read 1 byte. 727 elif sample_width == 2: 728 fmt = '%ih' # Read 2 bytes. 729 elif sample_width == 4: 730 fmt = '%ii' # Read 4 bytes. 731 else: 732 raise ValueError('Unsupported sample width') 733 return list(struct.unpack(fmt % num_frames * wave_file.getnchannels(), 734 wave_file.readframes(num_frames))) 735 736 737def check_wav_file(filename, num_channels=None, sample_rate=None, 738 sample_width=None): 739 """Checks a WAV file and returns its peak PCM values. 740 741 @param filename: Input WAV file to analyze. 742 @param num_channels: Number of channels to expect (None to not check). 743 @param sample_rate: Sample rate to expect (None to not check). 744 @param sample_width: Sample width to expect (None to not check). 745 746 @return A list of the absolute maximum PCM values for each channel in the 747 WAV file. 748 749 @raise ValueError: Failed to process the WAV file or validate an attribute. 750 """ 751 chk_file = None 752 try: 753 chk_file = wave.open(filename, 'r') 754 if num_channels is not None and chk_file.getnchannels() != num_channels: 755 raise ValueError('Expected %d channels but got %d instead.', 756 num_channels, chk_file.getnchannels()) 757 if sample_rate is not None and chk_file.getframerate() != sample_rate: 758 raise ValueError('Expected sample rate %d but got %d instead.', 759 sample_rate, chk_file.getframerate()) 760 if sample_width is not None and chk_file.getsampwidth() != sample_width: 761 raise ValueError('Expected sample width %d but got %d instead.', 762 sample_width, chk_file.getsampwidth()) 763 frames = extract_wav_frames(chk_file) 764 except wave.Error as e: 765 raise ValueError('Error processing WAV file: %s' % e) 766 finally: 767 if chk_file is not None: 768 chk_file.close() 769 770 # Since 8-bit PCM is unsigned with an offset of 128, we subtract the offset 771 # to make it signed since the rest of the code assumes signed numbers. 772 if chk_file.getsampwidth() == 1: 773 frames = [val - 128 for val in frames] 774 775 peaks = [] 776 for i in range(chk_file.getnchannels()): 777 peaks.append(max(map(abs, frames[i::chk_file.getnchannels()]))) 778 return peaks; 779 780 781def which(exec_file): 782 """Finds an executable file. 783 784 If the file name contains a path component, it is checked as-is. 785 Otherwise, we check with each of the path components found in the system 786 PATH prepended. This behavior is similar to the 'which' command-line tool. 787 788 @param exec_file: Name or path to desired executable. 789 790 @return: An actual path to the executable, or None if not found. 791 """ 792 if os.path.dirname(exec_file): 793 return exec_file if os.access(exec_file, os.X_OK) else None 794 sys_path = os.environ.get('PATH') 795 prefix_list = sys_path.split(os.pathsep) if sys_path else [] 796 for prefix in prefix_list: 797 path = os.path.join(prefix, exec_file) 798 if os.access(path, os.X_OK): 799 return path 800