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