1#!/usr/bin/env python3 2# 3# Copyright 2016 - The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17import base64 18import concurrent.futures 19import copy 20import datetime 21import functools 22import ipaddress 23import json 24import logging 25import os 26import platform 27import psutil 28import random 29import re 30import signal 31import string 32import socket 33import subprocess 34import time 35import threading 36import traceback 37import zipfile 38from concurrent.futures import ThreadPoolExecutor 39from zeroconf import IPVersion, Zeroconf 40 41from acts import signals 42from acts.controllers.adb_lib.error import AdbError 43from acts.libs.proc import job 44 45# File name length is limited to 255 chars on some OS, so we need to make sure 46# the file names we output fits within the limit. 47MAX_FILENAME_LEN = 255 48 49 50class ActsUtilsError(Exception): 51 """Generic error raised for exceptions in ACTS utils.""" 52 53 54class NexusModelNames: 55 # TODO(angli): This will be fixed later by angli. 56 ONE = 'sprout' 57 N5 = 'hammerhead' 58 N5v2 = 'bullhead' 59 N6 = 'shamu' 60 N6v2 = 'angler' 61 N6v3 = 'marlin' 62 N5v3 = 'sailfish' 63 64 65class DozeModeStatus: 66 ACTIVE = "ACTIVE" 67 IDLE = "IDLE" 68 69 70ascii_letters_and_digits = string.ascii_letters + string.digits 71valid_filename_chars = "-_." + ascii_letters_and_digits 72 73models = ("sprout", "occam", "hammerhead", "bullhead", "razor", "razorg", 74 "shamu", "angler", "volantis", "volantisg", "mantaray", "fugu", 75 "ryu", "marlin", "sailfish") 76 77manufacture_name_to_model = { 78 "flo": "razor", 79 "flo_lte": "razorg", 80 "flounder": "volantis", 81 "flounder_lte": "volantisg", 82 "dragon": "ryu" 83} 84 85GMT_to_olson = { 86 "GMT-9": "America/Anchorage", 87 "GMT-8": "US/Pacific", 88 "GMT-7": "US/Mountain", 89 "GMT-6": "US/Central", 90 "GMT-5": "US/Eastern", 91 "GMT-4": "America/Barbados", 92 "GMT-3": "America/Buenos_Aires", 93 "GMT-2": "Atlantic/South_Georgia", 94 "GMT-1": "Atlantic/Azores", 95 "GMT+0": "Africa/Casablanca", 96 "GMT+1": "Europe/Amsterdam", 97 "GMT+2": "Europe/Athens", 98 "GMT+3": "Europe/Moscow", 99 "GMT+4": "Asia/Baku", 100 "GMT+5": "Asia/Oral", 101 "GMT+6": "Asia/Almaty", 102 "GMT+7": "Asia/Bangkok", 103 "GMT+8": "Asia/Hong_Kong", 104 "GMT+9": "Asia/Tokyo", 105 "GMT+10": "Pacific/Guam", 106 "GMT+11": "Pacific/Noumea", 107 "GMT+12": "Pacific/Fiji", 108 "GMT+13": "Pacific/Tongatapu", 109 "GMT-11": "Pacific/Midway", 110 "GMT-10": "Pacific/Honolulu" 111} 112 113 114def abs_path(path): 115 """Resolve the '.' and '~' in a path to get the absolute path. 116 117 Args: 118 path: The path to expand. 119 120 Returns: 121 The absolute path of the input path. 122 """ 123 return os.path.abspath(os.path.expanduser(path)) 124 125 126def get_current_epoch_time(): 127 """Current epoch time in milliseconds. 128 129 Returns: 130 An integer representing the current epoch time in milliseconds. 131 """ 132 return int(round(time.time() * 1000)) 133 134 135def get_current_human_time(): 136 """Returns the current time in human readable format. 137 138 Returns: 139 The current time stamp in Month-Day-Year Hour:Min:Sec format. 140 """ 141 return time.strftime("%m-%d-%Y %H:%M:%S ") 142 143 144def epoch_to_human_time(epoch_time): 145 """Converts an epoch timestamp to human readable time. 146 147 This essentially converts an output of get_current_epoch_time to an output 148 of get_current_human_time 149 150 Args: 151 epoch_time: An integer representing an epoch timestamp in milliseconds. 152 153 Returns: 154 A time string representing the input time. 155 None if input param is invalid. 156 """ 157 if isinstance(epoch_time, int): 158 try: 159 d = datetime.datetime.fromtimestamp(epoch_time / 1000) 160 return d.strftime("%m-%d-%Y %H:%M:%S ") 161 except ValueError: 162 return None 163 164 165def get_timezone_olson_id(): 166 """Return the Olson ID of the local (non-DST) timezone. 167 168 Returns: 169 A string representing one of the Olson IDs of the local (non-DST) 170 timezone. 171 """ 172 tzoffset = int(time.timezone / 3600) 173 gmt = None 174 if tzoffset <= 0: 175 gmt = "GMT+{}".format(-tzoffset) 176 else: 177 gmt = "GMT-{}".format(tzoffset) 178 return GMT_to_olson[gmt] 179 180 181def get_next_device(test_bed_controllers, used_devices): 182 """Gets the next device in a list of testbed controllers 183 184 Args: 185 test_bed_controllers: A list of testbed controllers of a particular 186 type, for example a list ACTS Android devices. 187 used_devices: A list of devices that have been used. This can be a 188 mix of devices, for example a fuchsia device and an Android device. 189 Returns: 190 The next device in the test_bed_controllers list or None if there are 191 no items that are not in the used devices list. 192 """ 193 if test_bed_controllers: 194 device_list = test_bed_controllers 195 else: 196 raise ValueError('test_bed_controllers is empty.') 197 for used_device in used_devices: 198 if used_device in device_list: 199 device_list.remove(used_device) 200 if device_list: 201 return device_list[0] 202 else: 203 return None 204 205 206def find_files(paths, file_predicate): 207 """Locate files whose names and extensions match the given predicate in 208 the specified directories. 209 210 Args: 211 paths: A list of directory paths where to find the files. 212 file_predicate: A function that returns True if the file name and 213 extension are desired. 214 215 Returns: 216 A list of files that match the predicate. 217 """ 218 file_list = [] 219 if not isinstance(paths, list): 220 paths = [paths] 221 for path in paths: 222 p = abs_path(path) 223 for dirPath, subdirList, fileList in os.walk(p): 224 for fname in fileList: 225 name, ext = os.path.splitext(fname) 226 if file_predicate(name, ext): 227 file_list.append((dirPath, name, ext)) 228 return file_list 229 230 231def load_config(file_full_path, log_errors=True): 232 """Loads a JSON config file. 233 234 Returns: 235 A JSON object. 236 """ 237 with open(file_full_path, 'r') as f: 238 try: 239 return json.load(f) 240 except Exception as e: 241 if log_errors: 242 logging.error("Exception error to load %s: %s", f, e) 243 raise 244 245 246def load_file_to_base64_str(f_path): 247 """Loads the content of a file into a base64 string. 248 249 Args: 250 f_path: full path to the file including the file name. 251 252 Returns: 253 A base64 string representing the content of the file in utf-8 encoding. 254 """ 255 path = abs_path(f_path) 256 with open(path, 'rb') as f: 257 f_bytes = f.read() 258 base64_str = base64.b64encode(f_bytes).decode("utf-8") 259 return base64_str 260 261 262def dump_string_to_file(content, file_path, mode='w'): 263 """ Dump content of a string to 264 265 Args: 266 content: content to be dumped to file 267 file_path: full path to the file including the file name. 268 mode: file open mode, 'w' (truncating file) by default 269 :return: 270 """ 271 full_path = abs_path(file_path) 272 with open(full_path, mode) as f: 273 f.write(content) 274 275 276def list_of_dict_to_dict_of_dict(list_of_dicts, dict_key): 277 """Transforms a list of dicts to a dict of dicts. 278 279 For instance: 280 >>> list_of_dict_to_dict_of_dict([{'a': '1', 'b':'2'}, 281 >>> {'a': '3', 'b':'4'}], 282 >>> 'b') 283 284 returns: 285 286 >>> {'2': {'a': '1', 'b':'2'}, 287 >>> '4': {'a': '3', 'b':'4'}} 288 289 Args: 290 list_of_dicts: A list of dictionaries. 291 dict_key: The key in the inner dict to be used as the key for the 292 outer dict. 293 Returns: 294 A dict of dicts. 295 """ 296 return {d[dict_key]: d for d in list_of_dicts} 297 298 299def dict_purge_key_if_value_is_none(dictionary): 300 """Removes all pairs with value None from dictionary.""" 301 for k, v in dict(dictionary).items(): 302 if v is None: 303 del dictionary[k] 304 return dictionary 305 306 307def find_field(item_list, cond, comparator, target_field): 308 """Finds the value of a field in a dict object that satisfies certain 309 conditions. 310 311 Args: 312 item_list: A list of dict objects. 313 cond: A param that defines the condition. 314 comparator: A function that checks if an dict satisfies the condition. 315 target_field: Name of the field whose value to be returned if an item 316 satisfies the condition. 317 318 Returns: 319 Target value or None if no item satisfies the condition. 320 """ 321 for item in item_list: 322 if comparator(item, cond) and target_field in item: 323 return item[target_field] 324 return None 325 326 327def rand_ascii_str(length): 328 """Generates a random string of specified length, composed of ascii letters 329 and digits. 330 331 Args: 332 length: The number of characters in the string. 333 334 Returns: 335 The random string generated. 336 """ 337 letters = [random.choice(ascii_letters_and_digits) for i in range(length)] 338 return ''.join(letters) 339 340 341def rand_hex_str(length): 342 """Generates a random string of specified length, composed of hex digits 343 344 Args: 345 length: The number of characters in the string. 346 347 Returns: 348 The random string generated. 349 """ 350 letters = [random.choice(string.hexdigits) for i in range(length)] 351 return ''.join(letters) 352 353 354# Thead/Process related functions. 355def concurrent_exec(func, param_list): 356 """Executes a function with different parameters pseudo-concurrently. 357 358 This is basically a map function. Each element (should be an iterable) in 359 the param_list is unpacked and passed into the function. Due to Python's 360 GIL, there's no true concurrency. This is suited for IO-bound tasks. 361 362 Args: 363 func: The function that parforms a task. 364 param_list: A list of iterables, each being a set of params to be 365 passed into the function. 366 367 Returns: 368 A list of return values from each function execution. If an execution 369 caused an exception, the exception object will be the corresponding 370 result. 371 """ 372 with concurrent.futures.ThreadPoolExecutor(max_workers=30) as executor: 373 # Start the load operations and mark each future with its params 374 future_to_params = {executor.submit(func, *p): p for p in param_list} 375 return_vals = [] 376 for future in concurrent.futures.as_completed(future_to_params): 377 params = future_to_params[future] 378 try: 379 return_vals.append(future.result()) 380 except Exception as exc: 381 print("{} generated an exception: {}".format( 382 params, traceback.format_exc())) 383 return_vals.append(exc) 384 return return_vals 385 386 387def exe_cmd(*cmds): 388 """Executes commands in a new shell. 389 390 Args: 391 cmds: A sequence of commands and arguments. 392 393 Returns: 394 The output of the command run. 395 396 Raises: 397 OSError is raised if an error occurred during the command execution. 398 """ 399 cmd = ' '.join(cmds) 400 proc = subprocess.Popen(cmd, 401 stdout=subprocess.PIPE, 402 stderr=subprocess.PIPE, 403 shell=True) 404 (out, err) = proc.communicate() 405 if not err: 406 return out 407 raise OSError(err) 408 409 410def require_sl4a(android_devices): 411 """Makes sure sl4a connection is established on the given AndroidDevice 412 objects. 413 414 Args: 415 android_devices: A list of AndroidDevice objects. 416 417 Raises: 418 AssertionError is raised if any given android device does not have SL4A 419 connection established. 420 """ 421 for ad in android_devices: 422 msg = "SL4A connection not established properly on %s." % ad.serial 423 assert ad.droid, msg 424 425 426def _assert_subprocess_running(proc): 427 """Checks if a subprocess has terminated on its own. 428 429 Args: 430 proc: A subprocess returned by subprocess.Popen. 431 432 Raises: 433 ActsUtilsError is raised if the subprocess has stopped. 434 """ 435 ret = proc.poll() 436 if ret is not None: 437 out, err = proc.communicate() 438 raise ActsUtilsError("Process %d has terminated. ret: %d, stderr: %s," 439 " stdout: %s" % (proc.pid, ret, err, out)) 440 441 442def start_standing_subprocess(cmd, check_health_delay=0, shell=True): 443 """Starts a long-running subprocess. 444 445 This is not a blocking call and the subprocess started by it should be 446 explicitly terminated with stop_standing_subprocess. 447 448 For short-running commands, you should use exe_cmd, which blocks. 449 450 You can specify a health check after the subprocess is started to make sure 451 it did not stop prematurely. 452 453 Args: 454 cmd: string, the command to start the subprocess with. 455 check_health_delay: float, the number of seconds to wait after the 456 subprocess starts to check its health. Default is 0, 457 which means no check. 458 459 Returns: 460 The subprocess that got started. 461 """ 462 proc = subprocess.Popen(cmd, 463 stdout=subprocess.PIPE, 464 stderr=subprocess.PIPE, 465 shell=shell, 466 preexec_fn=os.setpgrp) 467 logging.debug("Start standing subprocess with cmd: %s", cmd) 468 if check_health_delay > 0: 469 time.sleep(check_health_delay) 470 _assert_subprocess_running(proc) 471 return proc 472 473 474def stop_standing_subprocess(proc, kill_signal=signal.SIGTERM): 475 """Stops a subprocess started by start_standing_subprocess. 476 477 Before killing the process, we check if the process is running, if it has 478 terminated, ActsUtilsError is raised. 479 480 Catches and ignores the PermissionError which only happens on Macs. 481 482 Args: 483 proc: Subprocess to terminate. 484 """ 485 pid = proc.pid 486 logging.debug("Stop standing subprocess %d", pid) 487 _assert_subprocess_running(proc) 488 try: 489 os.killpg(pid, kill_signal) 490 except PermissionError: 491 pass 492 493 494def wait_for_standing_subprocess(proc, timeout=None): 495 """Waits for a subprocess started by start_standing_subprocess to finish 496 or times out. 497 498 Propagates the exception raised by the subprocess.wait(.) function. 499 The subprocess.TimeoutExpired exception is raised if the process timed-out 500 rather then terminating. 501 502 If no exception is raised: the subprocess terminated on its own. No need 503 to call stop_standing_subprocess() to kill it. 504 505 If an exception is raised: the subprocess is still alive - it did not 506 terminate. Either call stop_standing_subprocess() to kill it, or call 507 wait_for_standing_subprocess() to keep waiting for it to terminate on its 508 own. 509 510 Args: 511 p: Subprocess to wait for. 512 timeout: An integer number of seconds to wait before timing out. 513 """ 514 proc.wait(timeout) 515 516 517def sync_device_time(ad): 518 """Sync the time of an android device with the current system time. 519 520 Both epoch time and the timezone will be synced. 521 522 Args: 523 ad: The android device to sync time on. 524 """ 525 ad.adb.shell("settings put global auto_time 0", ignore_status=True) 526 ad.adb.shell("settings put global auto_time_zone 0", ignore_status=True) 527 droid = ad.droid 528 droid.setTimeZone(get_timezone_olson_id()) 529 droid.setTime(get_current_epoch_time()) 530 531 532# Timeout decorator block 533class TimeoutError(Exception): 534 """Exception for timeout decorator related errors. 535 """ 536 pass 537 538 539def _timeout_handler(signum, frame): 540 """Handler function used by signal to terminate a timed out function. 541 """ 542 raise TimeoutError() 543 544 545def timeout(sec): 546 """A decorator used to add time out check to a function. 547 548 This only works in main thread due to its dependency on signal module. 549 Do NOT use it if the decorated funtion does not run in the Main thread. 550 551 Args: 552 sec: Number of seconds to wait before the function times out. 553 No timeout if set to 0 554 555 Returns: 556 What the decorated function returns. 557 558 Raises: 559 TimeoutError is raised when time out happens. 560 """ 561 def decorator(func): 562 @functools.wraps(func) 563 def wrapper(*args, **kwargs): 564 if sec: 565 signal.signal(signal.SIGALRM, _timeout_handler) 566 signal.alarm(sec) 567 try: 568 return func(*args, **kwargs) 569 except TimeoutError: 570 raise TimeoutError(("Function {} timed out after {} " 571 "seconds.").format(func.__name__, sec)) 572 finally: 573 signal.alarm(0) 574 575 return wrapper 576 577 return decorator 578 579 580def trim_model_name(model): 581 """Trim any prefix and postfix and return the android designation of the 582 model name. 583 584 e.g. "m_shamu" will be trimmed to "shamu". 585 586 Args: 587 model: model name to be trimmed. 588 589 Returns 590 Trimmed model name if one of the known model names is found. 591 None otherwise. 592 """ 593 # Directly look up first. 594 if model in models: 595 return model 596 if model in manufacture_name_to_model: 597 return manufacture_name_to_model[model] 598 # If not found, try trimming off prefix/postfix and look up again. 599 tokens = re.split("_|-", model) 600 for t in tokens: 601 if t in models: 602 return t 603 if t in manufacture_name_to_model: 604 return manufacture_name_to_model[t] 605 return None 606 607 608def force_airplane_mode(ad, new_state, timeout_value=60): 609 """Force the device to set airplane mode on or off by adb shell command. 610 611 Args: 612 ad: android device object. 613 new_state: Turn on airplane mode if True. 614 Turn off airplane mode if False. 615 timeout_value: max wait time for 'adb wait-for-device' 616 617 Returns: 618 True if success. 619 False if timeout. 620 """ 621 622 # Using timeout decorator. 623 # Wait for device with timeout. If after <timeout_value> seconds, adb 624 # is still waiting for device, throw TimeoutError exception. 625 @timeout(timeout_value) 626 def wait_for_device_with_timeout(ad): 627 ad.adb.wait_for_device() 628 629 try: 630 wait_for_device_with_timeout(ad) 631 ad.adb.shell("settings put global airplane_mode_on {}".format( 632 1 if new_state else 0)) 633 ad.adb.shell("am broadcast -a android.intent.action.AIRPLANE_MODE") 634 except TimeoutError: 635 # adb wait for device timeout 636 return False 637 return True 638 639 640def get_battery_level(ad): 641 """Gets battery level from device 642 643 Returns: 644 battery_level: int indicating battery level 645 """ 646 output = ad.adb.shell("dumpsys battery") 647 match = re.search(r"level: (?P<battery_level>\S+)", output) 648 battery_level = int(match.group("battery_level")) 649 return battery_level 650 651 652def get_device_usb_charging_status(ad): 653 """ Returns the usb charging status of the device. 654 655 Args: 656 ad: android device object 657 658 Returns: 659 True if charging 660 False if not charging 661 """ 662 adb_shell_result = ad.adb.shell("dumpsys deviceidle get charging") 663 ad.log.info("Device Charging State: {}".format(adb_shell_result)) 664 return adb_shell_result == 'true' 665 666 667def disable_usb_charging(ad): 668 """ Unplug device from usb charging. 669 670 Args: 671 ad: android device object 672 673 Returns: 674 True if device is unplugged 675 False otherwise 676 """ 677 ad.adb.shell("dumpsys battery unplug") 678 if not get_device_usb_charging_status(ad): 679 return True 680 else: 681 ad.log.info("Could not disable USB charging") 682 return False 683 684 685def enable_usb_charging(ad): 686 """ Plug device to usb charging. 687 688 Args: 689 ad: android device object 690 691 Returns: 692 True if device is Plugged 693 False otherwise 694 """ 695 ad.adb.shell("dumpsys battery reset") 696 if get_device_usb_charging_status(ad): 697 return True 698 else: 699 ad.log.info("Could not enable USB charging") 700 return False 701 702 703def enable_doze(ad): 704 """Force the device into doze mode. 705 706 Args: 707 ad: android device object. 708 709 Returns: 710 True if device is in doze mode. 711 False otherwise. 712 """ 713 ad.adb.shell("dumpsys battery unplug") 714 ad.adb.shell("dumpsys deviceidle enable") 715 ad.adb.shell("dumpsys deviceidle force-idle") 716 ad.droid.goToSleepNow() 717 time.sleep(5) 718 adb_shell_result = ad.adb.shell("dumpsys deviceidle get deep") 719 if not adb_shell_result.startswith(DozeModeStatus.IDLE): 720 info = ("dumpsys deviceidle get deep: {}".format(adb_shell_result)) 721 print(info) 722 return False 723 return True 724 725 726def disable_doze(ad): 727 """Force the device not in doze mode. 728 729 Args: 730 ad: android device object. 731 732 Returns: 733 True if device is not in doze mode. 734 False otherwise. 735 """ 736 ad.adb.shell("dumpsys deviceidle disable") 737 ad.adb.shell("dumpsys battery reset") 738 adb_shell_result = ad.adb.shell("dumpsys deviceidle get deep") 739 if not adb_shell_result.startswith(DozeModeStatus.ACTIVE): 740 info = ("dumpsys deviceidle get deep: {}".format(adb_shell_result)) 741 print(info) 742 return False 743 return True 744 745 746def enable_doze_light(ad): 747 """Force the device into doze light mode. 748 749 Args: 750 ad: android device object. 751 752 Returns: 753 True if device is in doze light mode. 754 False otherwise. 755 """ 756 ad.adb.shell("dumpsys battery unplug") 757 ad.droid.goToSleepNow() 758 time.sleep(5) 759 ad.adb.shell("cmd deviceidle enable light") 760 ad.adb.shell("cmd deviceidle step light") 761 adb_shell_result = ad.adb.shell("dumpsys deviceidle get light") 762 if not adb_shell_result.startswith(DozeModeStatus.IDLE): 763 info = ("dumpsys deviceidle get light: {}".format(adb_shell_result)) 764 print(info) 765 return False 766 return True 767 768 769def disable_doze_light(ad): 770 """Force the device not in doze light mode. 771 772 Args: 773 ad: android device object. 774 775 Returns: 776 True if device is not in doze light mode. 777 False otherwise. 778 """ 779 ad.adb.shell("dumpsys battery reset") 780 ad.adb.shell("cmd deviceidle disable light") 781 adb_shell_result = ad.adb.shell("dumpsys deviceidle get light") 782 if not adb_shell_result.startswith(DozeModeStatus.ACTIVE): 783 info = ("dumpsys deviceidle get light: {}".format(adb_shell_result)) 784 print(info) 785 return False 786 return True 787 788 789def set_ambient_display(ad, new_state): 790 """Set "Ambient Display" in Settings->Display 791 792 Args: 793 ad: android device object. 794 new_state: new state for "Ambient Display". True or False. 795 """ 796 ad.adb.shell( 797 "settings put secure doze_enabled {}".format(1 if new_state else 0)) 798 799 800def set_adaptive_brightness(ad, new_state): 801 """Set "Adaptive Brightness" in Settings->Display 802 803 Args: 804 ad: android device object. 805 new_state: new state for "Adaptive Brightness". True or False. 806 """ 807 ad.adb.shell("settings put system screen_brightness_mode {}".format( 808 1 if new_state else 0)) 809 810 811def set_auto_rotate(ad, new_state): 812 """Set "Auto-rotate" in QuickSetting 813 814 Args: 815 ad: android device object. 816 new_state: new state for "Auto-rotate". True or False. 817 """ 818 ad.adb.shell("settings put system accelerometer_rotation {}".format( 819 1 if new_state else 0)) 820 821 822def set_location_service(ad, new_state): 823 """Set Location service on/off in Settings->Location 824 825 Args: 826 ad: android device object. 827 new_state: new state for "Location service". 828 If new_state is False, turn off location service. 829 If new_state if True, set location service to "High accuracy". 830 """ 831 ad.adb.shell("content insert --uri " 832 " content://com.google.settings/partner --bind " 833 "name:s:network_location_opt_in --bind value:s:1") 834 ad.adb.shell("content insert --uri " 835 " content://com.google.settings/partner --bind " 836 "name:s:use_location_for_services --bind value:s:1") 837 if new_state: 838 ad.adb.shell("settings put secure location_mode 3") 839 else: 840 ad.adb.shell("settings put secure location_mode 0") 841 842 843def set_mobile_data_always_on(ad, new_state): 844 """Set Mobile_Data_Always_On feature bit 845 846 Args: 847 ad: android device object. 848 new_state: new state for "mobile_data_always_on" 849 if new_state is False, set mobile_data_always_on disabled. 850 if new_state if True, set mobile_data_always_on enabled. 851 """ 852 ad.adb.shell("settings put global mobile_data_always_on {}".format( 853 1 if new_state else 0)) 854 855 856def bypass_setup_wizard(ad): 857 """Bypass the setup wizard on an input Android device 858 859 Args: 860 ad: android device object. 861 862 Returns: 863 True if Android device successfully bypassed the setup wizard. 864 False if failed. 865 """ 866 try: 867 ad.adb.shell("am start -n \"com.google.android.setupwizard/" 868 ".SetupWizardExitActivity\"") 869 logging.debug("No error during default bypass call.") 870 except AdbError as adb_error: 871 if adb_error.stdout == "ADB_CMD_OUTPUT:0": 872 if adb_error.stderr and \ 873 not adb_error.stderr.startswith("Error type 3\n"): 874 logging.error("ADB_CMD_OUTPUT:0, but error is %s " % 875 adb_error.stderr) 876 raise adb_error 877 logging.debug("Bypass wizard call received harmless error 3: " 878 "No setup to bypass.") 879 elif adb_error.stdout == "ADB_CMD_OUTPUT:255": 880 # Run it again as root. 881 ad.adb.root_adb() 882 logging.debug("Need root access to bypass setup wizard.") 883 try: 884 ad.adb.shell("am start -n \"com.google.android.setupwizard/" 885 ".SetupWizardExitActivity\"") 886 logging.debug("No error during rooted bypass call.") 887 except AdbError as adb_error: 888 if adb_error.stdout == "ADB_CMD_OUTPUT:0": 889 if adb_error.stderr and \ 890 not adb_error.stderr.startswith("Error type 3\n"): 891 logging.error("Rooted ADB_CMD_OUTPUT:0, but error is " 892 "%s " % adb_error.stderr) 893 raise adb_error 894 logging.debug( 895 "Rooted bypass wizard call received harmless " 896 "error 3: No setup to bypass.") 897 898 # magical sleep to wait for the gservices override broadcast to complete 899 time.sleep(3) 900 901 provisioned_state = int( 902 ad.adb.shell("settings get global device_provisioned")) 903 if provisioned_state != 1: 904 logging.error("Failed to bypass setup wizard.") 905 return False 906 logging.debug("Setup wizard successfully bypassed.") 907 return True 908 909 910def parse_ping_ouput(ad, count, out, loss_tolerance=20): 911 """Ping Parsing util. 912 913 Args: 914 ad: Android Device Object. 915 count: Number of ICMP packets sent 916 out: shell output text of ping operation 917 loss_tolerance: Threshold after which flag test as false 918 Returns: 919 False: if packet loss is more than loss_tolerance% 920 True: if all good 921 """ 922 result = re.search( 923 r"(\d+) packets transmitted, (\d+) received, (\d+)% packet loss", out) 924 if not result: 925 ad.log.info("Ping failed with %s", out) 926 return False 927 928 packet_loss = int(result.group(3)) 929 packet_xmit = int(result.group(1)) 930 packet_rcvd = int(result.group(2)) 931 min_packet_xmit_rcvd = (100 - loss_tolerance) * 0.01 932 if (packet_loss > loss_tolerance 933 or packet_xmit < count * min_packet_xmit_rcvd 934 or packet_rcvd < count * min_packet_xmit_rcvd): 935 ad.log.error("%s, ping failed with loss more than tolerance %s%%", 936 result.group(0), loss_tolerance) 937 return False 938 ad.log.info("Ping succeed with %s", result.group(0)) 939 return True 940 941 942def adb_shell_ping(ad, 943 count=120, 944 dest_ip="www.google.com", 945 timeout=200, 946 loss_tolerance=20): 947 """Ping utility using adb shell. 948 949 Args: 950 ad: Android Device Object. 951 count: Number of ICMP packets to send 952 dest_ip: hostname or IP address 953 default www.google.com 954 timeout: timeout for icmp pings to complete. 955 """ 956 ping_cmd = "ping -W 1" 957 if count: 958 ping_cmd += " -c %d" % count 959 if dest_ip: 960 ping_cmd += " %s" % dest_ip 961 try: 962 ad.log.info("Starting ping test to %s using adb command %s", dest_ip, 963 ping_cmd) 964 out = ad.adb.shell(ping_cmd, timeout=timeout, ignore_status=True) 965 if not parse_ping_ouput(ad, count, out, loss_tolerance): 966 return False 967 return True 968 except Exception as e: 969 ad.log.warning("Ping Test to %s failed with exception %s", dest_ip, e) 970 return False 971 972 973def unzip_maintain_permissions(zip_path, extract_location): 974 """Unzip a .zip file while maintaining permissions. 975 976 Args: 977 zip_path: The path to the zipped file. 978 extract_location: the directory to extract to. 979 """ 980 with zipfile.ZipFile(zip_path, 'r') as zip_file: 981 for info in zip_file.infolist(): 982 _extract_file(zip_file, info, extract_location) 983 984 985def _extract_file(zip_file, zip_info, extract_location): 986 """Extracts a single entry from a ZipFile while maintaining permissions. 987 988 Args: 989 zip_file: A zipfile.ZipFile. 990 zip_info: A ZipInfo object from zip_file. 991 extract_location: The directory to extract to. 992 """ 993 out_path = zip_file.extract(zip_info.filename, path=extract_location) 994 perm = zip_info.external_attr >> 16 995 os.chmod(out_path, perm) 996 997 998def get_directory_size(path): 999 """Computes the total size of the files in a directory, including subdirectories. 1000 1001 Args: 1002 path: The path of the directory. 1003 Returns: 1004 The size of the provided directory. 1005 """ 1006 total = 0 1007 for dirpath, dirnames, filenames in os.walk(path): 1008 for filename in filenames: 1009 total += os.path.getsize(os.path.join(dirpath, filename)) 1010 return total 1011 1012 1013def get_command_uptime(command_regex): 1014 """Returns the uptime for a given command. 1015 1016 Args: 1017 command_regex: A regex that matches the command line given. Must be 1018 pgrep compatible. 1019 """ 1020 pid = job.run('pgrep -f %s' % command_regex).stdout 1021 runtime = '' 1022 if pid: 1023 runtime = job.run('ps -o etime= -p "%s"' % pid).stdout 1024 return runtime 1025 1026 1027def get_process_uptime(process): 1028 """Returns the runtime in [[dd-]hh:]mm:ss, or '' if not running.""" 1029 pid = job.run('pidof %s' % process, ignore_status=True).stdout 1030 runtime = '' 1031 if pid: 1032 runtime = job.run('ps -o etime= -p "%s"' % pid).stdout 1033 return runtime 1034 1035 1036def get_device_process_uptime(adb, process): 1037 """Returns the uptime of a device process.""" 1038 pid = adb.shell('pidof %s' % process, ignore_status=True) 1039 runtime = '' 1040 if pid: 1041 runtime = adb.shell('ps -o etime= -p "%s"' % pid) 1042 return runtime 1043 1044 1045def wait_until(func, timeout_s, condition=True, sleep_s=1.0): 1046 """Executes a function repeatedly until condition is met. 1047 1048 Args: 1049 func: The function pointer to execute. 1050 timeout_s: Amount of time (in seconds) to wait before raising an 1051 exception. 1052 condition: The ending condition of the WaitUntil loop. 1053 sleep_s: The amount of time (in seconds) to sleep between each function 1054 execution. 1055 1056 Returns: 1057 The time in seconds before detecting a successful condition. 1058 1059 Raises: 1060 TimeoutError: If the condition was never met and timeout is hit. 1061 """ 1062 start_time = time.time() 1063 end_time = start_time + timeout_s 1064 count = 0 1065 while True: 1066 count += 1 1067 if func() == condition: 1068 return time.time() - start_time 1069 if time.time() > end_time: 1070 break 1071 time.sleep(sleep_s) 1072 raise TimeoutError('Failed to complete function %s in %d seconds having ' 1073 'attempted %d times.' % (str(func), timeout_s, count)) 1074 1075 1076# Adapted from 1077# https://en.wikibooks.org/wiki/Algorithm_Implementation/Strings/Levenshtein_distance#Python 1078# Available under the Creative Commons Attribution-ShareAlike License 1079def levenshtein(string1, string2): 1080 """Returns the Levenshtein distance of two strings. 1081 Uses Dynamic Programming approach, only keeping track of 1082 two rows of the DP table at a time. 1083 1084 Args: 1085 string1: String to compare to string2 1086 string2: String to compare to string1 1087 1088 Returns: 1089 distance: the Levenshtein distance between string1 and string2 1090 """ 1091 1092 if len(string1) < len(string2): 1093 return levenshtein(string2, string1) 1094 1095 if len(string2) == 0: 1096 return len(string1) 1097 1098 previous_row = range(len(string2) + 1) 1099 for i, char1 in enumerate(string1): 1100 current_row = [i + 1] 1101 for j, char2 in enumerate(string2): 1102 insertions = previous_row[j + 1] + 1 1103 deletions = current_row[j] + 1 1104 substitutions = previous_row[j] + (char1 != char2) 1105 current_row.append(min(insertions, deletions, substitutions)) 1106 previous_row = current_row 1107 1108 return previous_row[-1] 1109 1110 1111def string_similarity(s1, s2): 1112 """Returns a similarity measurement based on Levenshtein distance. 1113 1114 Args: 1115 s1: the string to compare to s2 1116 s2: the string to compare to s1 1117 1118 Returns: 1119 result: the similarity metric 1120 """ 1121 lev = levenshtein(s1, s2) 1122 try: 1123 lev_ratio = float(lev) / max(len(s1), len(s2)) 1124 result = (1.0 - lev_ratio) * 100 1125 except ZeroDivisionError: 1126 result = 100 if not s2 else 0 1127 return float(result) 1128 1129 1130def run_concurrent_actions_no_raise(*calls): 1131 """Concurrently runs all callables passed in using multithreading. 1132 1133 Example: 1134 1135 >>> def test_function_1(arg1, arg2): 1136 >>> return arg1, arg2 1137 >>> 1138 >>> def test_function_2(arg1, kwarg='kwarg'): 1139 >>> raise arg1(kwarg) 1140 >>> 1141 >>> run_concurrent_actions_no_raise( 1142 >>> lambda: test_function_1('arg1', 'arg2'), 1143 >>> lambda: test_function_2(IndexError, kwarg='kwarg'), 1144 >>> ) 1145 >>> # Output: 1146 >>> [('arg1', 'arg2'), IndexError('kwarg')] 1147 1148 Args: 1149 *calls: A *args list of argumentless callable objects to be called. Note 1150 that if a function has arguments it can be turned into an 1151 argumentless function via the lambda keyword or functools.partial. 1152 1153 Returns: 1154 An array of the returned values or exceptions received from calls, 1155 respective of the order given. 1156 """ 1157 with ThreadPoolExecutor(max_workers=len(calls)) as executor: 1158 futures = [executor.submit(call) for call in calls] 1159 1160 results = [] 1161 for future in futures: 1162 try: 1163 results.append(future.result()) 1164 except Exception as e: 1165 results.append(e) 1166 return results 1167 1168 1169def run_concurrent_actions(*calls): 1170 """Runs all callables passed in concurrently using multithreading. 1171 1172 Examples: 1173 1174 >>> def test_function_1(arg1, arg2): 1175 >>> print(arg1, arg2) 1176 >>> 1177 >>> def test_function_2(arg1, kwarg='kwarg'): 1178 >>> raise arg1(kwarg) 1179 >>> 1180 >>> run_concurrent_actions( 1181 >>> lambda: test_function_1('arg1', 'arg2'), 1182 >>> lambda: test_function_2(IndexError, kwarg='kwarg'), 1183 >>> ) 1184 >>> 'The above line raises IndexError("kwarg")' 1185 1186 Args: 1187 *calls: A *args list of argumentless callable objects to be called. Note 1188 that if a function has arguments it can be turned into an 1189 argumentless function via the lambda keyword or functools.partial. 1190 1191 Returns: 1192 An array of the returned values respective of the order of the calls 1193 argument. 1194 1195 Raises: 1196 If an exception is raised in any of the calls, the first exception 1197 caught will be raised. 1198 """ 1199 first_exception = None 1200 1201 class WrappedException(Exception): 1202 """Raised when a passed-in callable raises an exception.""" 1203 1204 def call_wrapper(call): 1205 nonlocal first_exception 1206 1207 try: 1208 return call() 1209 except Exception as e: 1210 logging.exception(e) 1211 # Note that there is a potential race condition between two 1212 # exceptions setting first_exception. Even if a locking mechanism 1213 # was added to prevent this from happening, it is still possible 1214 # that we capture the second exception as the first exception, as 1215 # the active thread can swap to the thread that raises the second 1216 # exception. There is no way to solve this with the tools we have 1217 # here, so we do not bother. The effects this issue has on the 1218 # system as a whole are negligible. 1219 if first_exception is None: 1220 first_exception = e 1221 raise WrappedException(e) 1222 1223 with ThreadPoolExecutor(max_workers=len(calls)) as executor: 1224 futures = [executor.submit(call_wrapper, call) for call in calls] 1225 1226 results = [] 1227 for future in futures: 1228 try: 1229 results.append(future.result()) 1230 except WrappedException: 1231 # We do not need to raise here, since first_exception will already 1232 # be set to the first exception raised by these callables. 1233 break 1234 1235 if first_exception: 1236 raise first_exception 1237 1238 return results 1239 1240 1241def test_concurrent_actions(*calls, failure_exceptions=(Exception, )): 1242 """Concurrently runs all passed in calls using multithreading. 1243 1244 If any callable raises an Exception found within failure_exceptions, the 1245 test case is marked as a failure. 1246 1247 Example: 1248 >>> def test_function_1(arg1, arg2): 1249 >>> print(arg1, arg2) 1250 >>> 1251 >>> def test_function_2(kwarg='kwarg'): 1252 >>> raise IndexError(kwarg) 1253 >>> 1254 >>> test_concurrent_actions( 1255 >>> lambda: test_function_1('arg1', 'arg2'), 1256 >>> lambda: test_function_2(kwarg='kwarg'), 1257 >>> failure_exceptions=IndexError 1258 >>> ) 1259 >>> 'raises signals.TestFailure due to IndexError being raised.' 1260 1261 Args: 1262 *calls: A *args list of argumentless callable objects to be called. Note 1263 that if a function has arguments it can be turned into an 1264 argumentless function via the lambda keyword or functools.partial. 1265 failure_exceptions: A tuple of all possible Exceptions that will mark 1266 the test as a FAILURE. Any exception that is not in this list will 1267 mark the tests as UNKNOWN. 1268 1269 Returns: 1270 An array of the returned values respective of the order of the calls 1271 argument. 1272 1273 Raises: 1274 signals.TestFailure if any call raises an Exception. 1275 """ 1276 try: 1277 return run_concurrent_actions(*calls) 1278 except signals.TestFailure: 1279 # Do not modify incoming test failures 1280 raise 1281 except failure_exceptions as e: 1282 raise signals.TestFailure(e) 1283 1284 1285class SuppressLogOutput(object): 1286 """Context manager used to suppress all logging output for the specified 1287 logger and level(s). 1288 """ 1289 def __init__(self, logger=logging.getLogger(), log_levels=None): 1290 """Create a SuppressLogOutput context manager 1291 1292 Args: 1293 logger: The logger object to suppress 1294 log_levels: Levels of log handlers to disable. 1295 """ 1296 1297 self._logger = logger 1298 self._log_levels = log_levels or [ 1299 logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, 1300 logging.CRITICAL 1301 ] 1302 if isinstance(self._log_levels, int): 1303 self._log_levels = [self._log_levels] 1304 self._handlers = copy.copy(self._logger.handlers) 1305 1306 def __enter__(self): 1307 for handler in self._handlers: 1308 if handler.level in self._log_levels: 1309 self._logger.removeHandler(handler) 1310 return self 1311 1312 def __exit__(self, *_): 1313 for handler in self._handlers: 1314 self._logger.addHandler(handler) 1315 1316 1317class BlockingTimer(object): 1318 """Context manager used to block until a specified amount of time has 1319 elapsed. 1320 """ 1321 def __init__(self, secs): 1322 """Initializes a BlockingTimer 1323 1324 Args: 1325 secs: Number of seconds to wait before exiting 1326 """ 1327 self._thread = threading.Timer(secs, lambda: None) 1328 1329 def __enter__(self): 1330 self._thread.start() 1331 return self 1332 1333 def __exit__(self, *_): 1334 self._thread.join() 1335 1336 1337def is_valid_ipv4_address(address): 1338 try: 1339 socket.inet_pton(socket.AF_INET, address) 1340 except AttributeError: # no inet_pton here, sorry 1341 try: 1342 socket.inet_aton(address) 1343 except socket.error: 1344 return False 1345 return address.count('.') == 3 1346 except socket.error: # not a valid address 1347 return False 1348 1349 return True 1350 1351 1352def is_valid_ipv6_address(address): 1353 if '%' in address: 1354 address = address.split('%')[0] 1355 try: 1356 socket.inet_pton(socket.AF_INET6, address) 1357 except socket.error: # not a valid address 1358 return False 1359 return True 1360 1361 1362def merge_dicts(*dict_args): 1363 """ Merges args list of dictionaries into a single dictionary. 1364 1365 Args: 1366 dict_args: an args list of dictionaries to be merged. If multiple 1367 dictionaries share a key, the last in the list will appear in the 1368 final result. 1369 """ 1370 result = {} 1371 for dictionary in dict_args: 1372 result.update(dictionary) 1373 return result 1374 1375 1376def ascii_string(uc_string): 1377 """Converts unicode string to ascii""" 1378 return str(uc_string).encode('ASCII') 1379 1380 1381def get_interface_ip_addresses(comm_channel, interface): 1382 """Gets all of the ip addresses, ipv4 and ipv6, associated with a 1383 particular interface name. 1384 1385 Args: 1386 comm_channel: How to send commands to a device. Can be ssh, adb serial, 1387 etc. Must have the run function implemented. 1388 interface: The interface name on the device, ie eth0 1389 1390 Returns: 1391 A list of dictionaries of the the various IP addresses: 1392 ipv4_private_local_addresses: Any 192.168, 172.16, 10, or 169.254 1393 addresses 1394 ipv4_public_addresses: Any IPv4 public addresses 1395 ipv6_link_local_addresses: Any fe80:: addresses 1396 ipv6_private_local_addresses: Any fd00:: addresses 1397 ipv6_public_addresses: Any publicly routable addresses 1398 """ 1399 # Local imports are used here to prevent cyclic dependency. 1400 from acts.controllers.android_device import AndroidDevice 1401 from acts.controllers.fuchsia_device import FuchsiaDevice 1402 from acts.controllers.utils_lib.ssh.connection import SshConnection 1403 ipv4_private_local_addresses = [] 1404 ipv4_public_addresses = [] 1405 ipv6_link_local_addresses = [] 1406 ipv6_private_local_addresses = [] 1407 ipv6_public_addresses = [] 1408 is_local = comm_channel == job 1409 if type(comm_channel) is AndroidDevice: 1410 all_interfaces_and_addresses = comm_channel.adb.shell( 1411 'ip -o addr | awk \'!/^[0-9]*: ?lo|link\/ether/ {gsub("/", " "); ' 1412 'print $2" "$4}\'') 1413 ifconfig_output = comm_channel.adb.shell('ifconfig %s' % interface) 1414 elif (type(comm_channel) is SshConnection or is_local): 1415 all_interfaces_and_addresses = comm_channel.run( 1416 'ip -o addr | awk \'!/^[0-9]*: ?lo|link\/ether/ {gsub("/", " "); ' 1417 'print $2" "$4}\'').stdout 1418 ifconfig_output = comm_channel.run('ifconfig %s' % interface).stdout 1419 elif type(comm_channel) is FuchsiaDevice: 1420 all_interfaces_and_addresses = [] 1421 comm_channel.netstack_lib.init() 1422 interfaces = comm_channel.netstack_lib.netstackListInterfaces() 1423 if interfaces.get('error') is not None: 1424 raise ActsUtilsError('Failed with {}'.format( 1425 interfaces.get('error'))) 1426 for item in interfaces.get('result'): 1427 for ipv4_address in item['ipv4_addresses']: 1428 ipv4_address = '.'.join(map(str, ipv4_address)) 1429 all_interfaces_and_addresses.append( 1430 '%s %s' % (item['name'], ipv4_address)) 1431 for ipv6_address in item['ipv6_addresses']: 1432 converted_ipv6_address = [] 1433 for octet in ipv6_address: 1434 converted_ipv6_address.append(format(octet, 'x').zfill(2)) 1435 ipv6_address = ''.join(converted_ipv6_address) 1436 ipv6_address = (':'.join( 1437 ipv6_address[i:i + 4] 1438 for i in range(0, len(ipv6_address), 4))) 1439 all_interfaces_and_addresses.append( 1440 '%s %s' % 1441 (item['name'], str(ipaddress.ip_address(ipv6_address)))) 1442 all_interfaces_and_addresses = '\n'.join(all_interfaces_and_addresses) 1443 ifconfig_output = all_interfaces_and_addresses 1444 else: 1445 raise ValueError('Unsupported method to send command to device.') 1446 1447 for interface_line in all_interfaces_and_addresses.split('\n'): 1448 if interface != interface_line.split()[0]: 1449 continue 1450 on_device_ip = ipaddress.ip_address(interface_line.split()[1]) 1451 if on_device_ip.version == 4: 1452 if on_device_ip.is_private: 1453 if str(on_device_ip) in ifconfig_output: 1454 ipv4_private_local_addresses.append(str(on_device_ip)) 1455 elif on_device_ip.is_global or ( 1456 # Carrier private doesn't have a property, so we check if 1457 # all other values are left unset. 1458 not on_device_ip.is_reserved 1459 and not on_device_ip.is_unspecified 1460 and not on_device_ip.is_link_local 1461 and not on_device_ip.is_loopback 1462 and not on_device_ip.is_multicast): 1463 if str(on_device_ip) in ifconfig_output: 1464 ipv4_public_addresses.append(str(on_device_ip)) 1465 elif on_device_ip.version == 6: 1466 if on_device_ip.is_link_local: 1467 if str(on_device_ip) in ifconfig_output: 1468 ipv6_link_local_addresses.append(str(on_device_ip)) 1469 elif on_device_ip.is_private: 1470 if str(on_device_ip) in ifconfig_output: 1471 ipv6_private_local_addresses.append(str(on_device_ip)) 1472 elif on_device_ip.is_global: 1473 if str(on_device_ip) in ifconfig_output: 1474 ipv6_public_addresses.append(str(on_device_ip)) 1475 return { 1476 'ipv4_private': ipv4_private_local_addresses, 1477 'ipv4_public': ipv4_public_addresses, 1478 'ipv6_link_local': ipv6_link_local_addresses, 1479 'ipv6_private_local': ipv6_private_local_addresses, 1480 'ipv6_public': ipv6_public_addresses 1481 } 1482 1483 1484def get_interface_based_on_ip(comm_channel, desired_ip_address): 1485 """Gets the interface for a particular IP 1486 1487 Args: 1488 comm_channel: How to send commands to a device. Can be ssh, adb serial, 1489 etc. Must have the run function implemented. 1490 desired_ip_address: The IP address that is being looked for on a device. 1491 1492 Returns: 1493 The name of the test interface. 1494 """ 1495 1496 desired_ip_address = desired_ip_address.split('%', 1)[0] 1497 all_ips_and_interfaces = comm_channel.run( 1498 '(ip -o -4 addr show; ip -o -6 addr show) | ' 1499 'awk \'{print $2" "$4}\'').stdout 1500 #ipv4_addresses = comm_channel.run( 1501 # 'ip -o -4 addr show| awk \'{print $2": "$4}\'').stdout 1502 #ipv6_addresses = comm_channel._ssh_session.run( 1503 # 'ip -o -6 addr show| awk \'{print $2": "$4}\'').stdout 1504 #if desired_ip_address in ipv4_addresses: 1505 # ip_addresses_to_search = ipv4_addresses 1506 #elif desired_ip_address in ipv6_addresses: 1507 # ip_addresses_to_search = ipv6_addresses 1508 for ip_address_and_interface in all_ips_and_interfaces.split('\n'): 1509 if desired_ip_address in ip_address_and_interface: 1510 return ip_address_and_interface.split()[1][:-1] 1511 return None 1512 1513 1514def renew_linux_ip_address(comm_channel, interface): 1515 comm_channel.run('sudo ifconfig %s down' % interface) 1516 comm_channel.run('sudo ifconfig %s up' % interface) 1517 comm_channel.run('sudo dhclient -r %s' % interface) 1518 comm_channel.run('sudo dhclient %s' % interface) 1519 1520 1521def get_ping_command(dest_ip, 1522 count=3, 1523 interval=1000, 1524 timeout=1000, 1525 size=56, 1526 os_type='Linux', 1527 additional_ping_params=None): 1528 """Builds ping command string based on address type, os, and params. 1529 1530 Args: 1531 dest_ip: string, address to ping (ipv4 or ipv6) 1532 count: int, number of requests to send 1533 interval: int, time in seconds between requests 1534 timeout: int, time in seconds to wait for response 1535 size: int, number of bytes to send, 1536 os_type: string, os type of the source device (supports 'Linux', 1537 'Darwin') 1538 additional_ping_params: string, command option flags to 1539 append to the command string 1540 1541 Returns: 1542 List of string, represetning the ping command. 1543 """ 1544 if is_valid_ipv4_address(dest_ip): 1545 ping_binary = 'ping' 1546 elif is_valid_ipv6_address(dest_ip): 1547 ping_binary = 'ping6' 1548 else: 1549 raise ValueError('Invalid ip addr: %s' % dest_ip) 1550 1551 if os_type == 'Darwin': 1552 if is_valid_ipv6_address(dest_ip): 1553 # ping6 on MacOS doesn't support timeout 1554 logging.warn( 1555 'Ignoring timeout, as ping6 on MacOS does not support it.') 1556 timeout_flag = [] 1557 else: 1558 timeout_flag = ['-t', str(timeout / 1000)] 1559 elif os_type == 'Linux': 1560 timeout_flag = ['-W', str(timeout / 1000)] 1561 else: 1562 raise ValueError('Invalid OS. Only Linux and MacOS are supported.') 1563 1564 if not additional_ping_params: 1565 additional_ping_params = '' 1566 1567 ping_cmd = [ 1568 ping_binary, *timeout_flag, '-c', 1569 str(count), '-i', 1570 str(interval / 1000), '-s', 1571 str(size), additional_ping_params, dest_ip 1572 ] 1573 return ' '.join(ping_cmd) 1574 1575 1576def ping(comm_channel, 1577 dest_ip, 1578 count=3, 1579 interval=1000, 1580 timeout=1000, 1581 size=56, 1582 additional_ping_params=None): 1583 """ Generic linux ping function, supports local (acts.libs.proc.job) and 1584 SshConnections (acts.libs.proc.job over ssh) to Linux based OSs and MacOS. 1585 1586 NOTES: This will work with Android over SSH, but does not function over ADB 1587 as that has a unique return format. 1588 1589 Args: 1590 comm_channel: communication channel over which to send ping command. 1591 Must have 'run' function that returns at least command, stdout, 1592 stderr, and exit_status (see acts.libs.proc.job) 1593 dest_ip: address to ping (ipv4 or ipv6) 1594 count: int, number of packets to send 1595 interval: int, time in milliseconds between pings 1596 timeout: int, time in milliseconds to wait for response 1597 size: int, size of packets in bytes 1598 additional_ping_params: string, command option flags to 1599 append to the command string 1600 1601 Returns: 1602 Dict containing: 1603 command: string 1604 exit_status: int (0 or 1) 1605 stdout: string 1606 stderr: string 1607 transmitted: int, number of packets transmitted 1608 received: int, number of packets received 1609 packet_loss: int, percentage packet loss 1610 time: int, time of ping command execution (in milliseconds) 1611 rtt_min: float, minimum round trip time 1612 rtt_avg: float, average round trip time 1613 rtt_max: float, maximum round trip time 1614 rtt_mdev: float, round trip time standard deviation 1615 1616 Any values that cannot be parsed are left as None 1617 """ 1618 from acts.controllers.utils_lib.ssh.connection import SshConnection 1619 is_local = comm_channel == job 1620 os_type = platform.system() if is_local else 'Linux' 1621 ping_cmd = get_ping_command(dest_ip, 1622 count=count, 1623 interval=interval, 1624 timeout=timeout, 1625 size=size, 1626 os_type=os_type, 1627 additional_ping_params=additional_ping_params) 1628 1629 if (type(comm_channel) is SshConnection or is_local): 1630 logging.debug( 1631 'Running ping with parameters (count: %s, interval: %s, timeout: ' 1632 '%s, size: %s)' % (count, interval, timeout, size)) 1633 ping_result = comm_channel.run(ping_cmd, ignore_status=True) 1634 else: 1635 raise ValueError('Unsupported comm_channel: %s' % type(comm_channel)) 1636 1637 if isinstance(ping_result, job.Error): 1638 ping_result = ping_result.result 1639 1640 transmitted = None 1641 received = None 1642 packet_loss = None 1643 time = None 1644 rtt_min = None 1645 rtt_avg = None 1646 rtt_max = None 1647 rtt_mdev = None 1648 1649 summary = re.search( 1650 '([0-9]+) packets transmitted.*?([0-9]+) received.*?([0-9]+)% packet ' 1651 'loss.*?time ([0-9]+)', ping_result.stdout) 1652 if summary: 1653 transmitted = summary[1] 1654 received = summary[2] 1655 packet_loss = summary[3] 1656 time = summary[4] 1657 1658 rtt_stats = re.search('= ([0-9.]+)/([0-9.]+)/([0-9.]+)/([0-9.]+)', 1659 ping_result.stdout) 1660 if rtt_stats: 1661 rtt_min = rtt_stats[1] 1662 rtt_avg = rtt_stats[2] 1663 rtt_max = rtt_stats[3] 1664 rtt_mdev = rtt_stats[4] 1665 1666 return { 1667 'command': ping_result.command, 1668 'exit_status': ping_result.exit_status, 1669 'stdout': ping_result.stdout, 1670 'stderr': ping_result.stderr, 1671 'transmitted': transmitted, 1672 'received': received, 1673 'packet_loss': packet_loss, 1674 'time': time, 1675 'rtt_min': rtt_min, 1676 'rtt_avg': rtt_avg, 1677 'rtt_max': rtt_max, 1678 'rtt_mdev': rtt_mdev 1679 } 1680 1681 1682def can_ping(comm_channel, 1683 dest_ip, 1684 count=1, 1685 interval=1000, 1686 timeout=1000, 1687 size=56, 1688 additional_ping_params=None): 1689 """Returns whether device connected via comm_channel can ping a dest 1690 address""" 1691 ping_results = ping(comm_channel, 1692 dest_ip, 1693 count=count, 1694 interval=interval, 1695 timeout=timeout, 1696 size=size, 1697 additional_ping_params=additional_ping_params) 1698 1699 return ping_results['exit_status'] == 0 1700 1701 1702def ip_in_subnet(ip, subnet): 1703 """Validate that ip is in a given subnet. 1704 1705 Args: 1706 ip: string, ip address to verify (eg. '192.168.42.158') 1707 subnet: string, subnet to check (eg. '192.168.42.0/24') 1708 1709 Returns: 1710 True, if ip in subnet, else False 1711 """ 1712 return ipaddress.ip_address(ip) in ipaddress.ip_network(subnet) 1713 1714 1715def mac_address_str_to_list(mac_addr_str): 1716 """Converts mac address string to list of decimal octets. 1717 1718 Args: 1719 mac_addr_string: string, mac address 1720 e.g. '12:34:56:78:9a:bc' 1721 1722 Returns 1723 list, representing mac address octets in decimal 1724 e.g. [18, 52, 86, 120, 154, 188] 1725 """ 1726 return [int(octet, 16) for octet in mac_addr_str.split(':')] 1727 1728 1729def mac_address_list_to_str(mac_addr_list): 1730 """Converts list of decimal octets represeting mac address to string. 1731 1732 Args: 1733 mac_addr_list: list, representing mac address octets in decimal 1734 e.g. [18, 52, 86, 120, 154, 188] 1735 1736 Returns: 1737 string, mac address 1738 e.g. '12:34:56:78:9a:bc' 1739 """ 1740 hex_list = [] 1741 for octet in mac_addr_list: 1742 hex_octet = hex(octet)[2:] 1743 if octet < 16: 1744 hex_list.append('0%s' % hex_octet) 1745 else: 1746 hex_list.append(hex_octet) 1747 1748 return ':'.join(hex_list) 1749 1750 1751def get_fuchsia_mdns_ipv6_address(device_mdns_name): 1752 """Gets the ipv6 link local address from a fuchsia device over mdns 1753 1754 Args: 1755 device_mdns_name: name of fuchsia device, ie gig-clone-sugar-slash 1756 1757 Returns: 1758 string, ipv6 link local address 1759 """ 1760 if not device_mdns_name: 1761 return None 1762 mdns_type = '_fuchsia._udp.local.' 1763 interface_list = psutil.net_if_addrs() 1764 for interface in interface_list: 1765 interface_ipv6_link_local = \ 1766 get_interface_ip_addresses(job, interface)['ipv6_link_local'] 1767 if 'fe80::1' in interface_ipv6_link_local: 1768 logging.info('Removing IPv6 loopback IP from %s interface list.' 1769 ' Not modifying actual system IP addresses.' % 1770 interface) 1771 # This is needed as the Zeroconf library crashes if you try to 1772 # instantiate it on a IPv6 loopback IP address. 1773 interface_ipv6_link_local.remove('fe80::1') 1774 1775 if interface_ipv6_link_local: 1776 zeroconf = Zeroconf(ip_version=IPVersion.V6Only, 1777 interfaces=interface_ipv6_link_local) 1778 device_records = (zeroconf.get_service_info( 1779 mdns_type, device_mdns_name + '.' + mdns_type)) 1780 if device_records: 1781 for device_ip_address in device_records.parsed_addresses(): 1782 device_ip_address = ipaddress.ip_address(device_ip_address) 1783 if (device_ip_address.version == 6 1784 and device_ip_address.is_link_local): 1785 if ping(job, 1786 dest_ip='%s%%%s' % 1787 (str(device_ip_address), 1788 interface))['exit_status'] == 0: 1789 zeroconf.close() 1790 del zeroconf 1791 return ('%s%%%s' % 1792 (str(device_ip_address), interface)) 1793 zeroconf.close() 1794 del zeroconf 1795 logging.error('Unable to get ip address for %s' % device_mdns_name) 1796 return None 1797