1# Copyright (c) 2013 The Chromium OS Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4import functools 5import logging 6import os 7import re 8import stat 9import sys 10import time 11 12import common 13 14from autotest_lib.client.common_lib import error 15from autotest_lib.client.common_lib.cros import dev_server 16from autotest_lib.client.common_lib.cros import retry 17from autotest_lib.server import autoserv_parser 18from autotest_lib.server import constants as server_constants 19from autotest_lib.server import utils 20from autotest_lib.server.cros import provision 21from autotest_lib.server.cros.dynamic_suite import constants 22from autotest_lib.server.hosts import abstract_ssh 23from autotest_lib.server.hosts import teststation_host 24 25 26ADB_CMD = 'adb' 27FASTBOOT_CMD = 'fastboot' 28SHELL_CMD = 'shell' 29# Some devices have no serial, then `adb serial` has output such as: 30# (no serial number) device 31# ?????????? device 32DEVICE_NO_SERIAL_MSG = '(no serial number)' 33DEVICE_NO_SERIAL_TAG = '<NO_SERIAL>' 34# Regex to find an adb device. Examples: 35# 0146B5580B01801B device 36# 018e0ecb20c97a62 device 37# 172.22.75.141:5555 device 38DEVICE_FINDER_REGEX = ('^(?P<SERIAL>([\w]+)|(\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3})|' + 39 re.escape(DEVICE_NO_SERIAL_MSG) + 40 ')([:]5555)?[ \t]+(?:device|fastboot)') 41CMD_OUTPUT_PREFIX = 'ADB_CMD_OUTPUT' 42CMD_OUTPUT_REGEX = ('(?P<OUTPUT>[\s\S]*)%s:(?P<EXIT_CODE>\d{1,3})' % 43 CMD_OUTPUT_PREFIX) 44RELEASE_FILE = 'ro.build.version.release' 45BOARD_FILE = 'ro.product.device' 46TMP_DIR = '/data/local/tmp' 47# Regex to pull out file type, perms and symlink. Example: 48# lrwxrwx--- 1 6 root system 2015-09-12 19:21 blah_link -> ./blah 49FILE_INFO_REGEX = '^(?P<TYPE>[dl-])(?P<PERMS>[rwx-]{9})' 50FILE_SYMLINK_REGEX = '^.*-> (?P<SYMLINK>.+)' 51# List of the perm stats indexed by the order they are listed in the example 52# supplied above. 53FILE_PERMS_FLAGS = [stat.S_IRUSR, stat.S_IWUSR, stat.S_IXUSR, 54 stat.S_IRGRP, stat.S_IWGRP, stat.S_IXGRP, 55 stat.S_IROTH, stat.S_IWOTH, stat.S_IXOTH] 56 57# Default maximum number of seconds to wait for a device to be down. 58DEFAULT_WAIT_DOWN_TIME_SECONDS = 10 59# Default maximum number of seconds to wait for a device to be up. 60DEFAULT_WAIT_UP_TIME_SECONDS = 300 61# Maximum number of seconds to wait for a device to be up after it's wiped. 62WAIT_UP_AFTER_WIPE_TIME_SECONDS = 1200 63 64OS_TYPE_ANDROID = 'android' 65OS_TYPE_BRILLO = 'brillo' 66 67# Regex to parse build name to get the detailed build information. 68BUILD_REGEX = ('(?P<BRANCH>([^/]+))/(?P<BOARD>([^/]+))-' 69 '(?P<BUILD_TYPE>([^/]+))/(?P<BUILD_ID>([^/]+))') 70# Regex to parse devserver url to get the detailed build information. Sample 71# url: http://$devserver:8080/static/branch/target/build_id 72DEVSERVER_URL_REGEX = '.*/%s/*' % BUILD_REGEX 73 74ANDROID_IMAGE_FILE_FMT = '%(board)s-img-%(build_id)s.zip' 75ANDROID_BOOTLOADER = 'bootloader.img' 76ANDROID_RADIO = 'radio.img' 77ANDROID_BOOT = 'boot.img' 78ANDROID_SYSTEM = 'system.img' 79ANDROID_VENDOR = 'vendor.img' 80BRILLO_VENDOR_PARTITIONS_FILE_FMT = ( 81 '%(board)s-vendor_partitions-%(build_id)s.zip') 82 83# Image files not inside the image zip file. These files should be downloaded 84# directly from devserver. 85ANDROID_STANDALONE_IMAGES = [ANDROID_BOOTLOADER, ANDROID_RADIO] 86# Image files that are packaged in a zip file, e.g., shamu-img-123456.zip 87ANDROID_ZIPPED_IMAGES = [ANDROID_BOOT, ANDROID_SYSTEM, ANDROID_VENDOR] 88# All image files to be flashed to an Android device. 89ANDROID_IMAGES = ANDROID_STANDALONE_IMAGES + ANDROID_ZIPPED_IMAGES 90 91# Command to provision a Brillo device. 92# os_image_dir: The full path of the directory that contains all the Android image 93# files (from the image zip file). 94# vendor_partition_dir: The full path of the directory that contains all the 95# Brillo vendor partitions, and provision-device script. 96BRILLO_PROVISION_CMD = ( 97 'sudo ANDROID_PROVISION_OS_PARTITIONS=%(os_image_dir)s ' 98 'ANDROID_PROVISION_VENDOR_PARTITIONS=%(vendor_partition_dir)s ' 99 '%(vendor_partition_dir)s/provision-device') 100 101class AndroidInstallError(error.InstallError): 102 """Generic error for Android installation related exceptions.""" 103 104 105class ADBHost(abstract_ssh.AbstractSSHHost): 106 """This class represents a host running an ADB server.""" 107 108 VERSION_PREFIX = provision.ANDROID_BUILD_VERSION_PREFIX 109 _LABEL_FUNCTIONS = [] 110 _DETECTABLE_LABELS = [] 111 label_decorator = functools.partial(utils.add_label_detector, 112 _LABEL_FUNCTIONS, 113 _DETECTABLE_LABELS) 114 115 _parser = autoserv_parser.autoserv_parser 116 117 @staticmethod 118 def check_host(host, timeout=10): 119 """ 120 Check if the given host is an adb host. 121 122 If SSH connectivity can't be established, check_host will try to use 123 user 'adb' as well. If SSH connectivity still can't be established 124 then the original SSH user is restored. 125 126 @param host: An ssh host representing a device. 127 @param timeout: The timeout for the run command. 128 129 130 @return: True if the host device has adb. 131 132 @raises AutoservRunError: If the command failed. 133 @raises AutoservSSHTimeout: Ssh connection has timed out. 134 """ 135 # host object may not have user attribute if it's a LocalHost object. 136 current_user = host.user if hasattr(host, 'user') else None 137 try: 138 if not (host.hostname == 'localhost' or 139 host.verify_ssh_user_access()): 140 host.user = 'adb' 141 result = host.run( 142 'test -f %s' % server_constants.ANDROID_TESTER_FILEFLAG, 143 timeout=timeout) 144 except (error.AutoservRunError, error.AutoservSSHTimeout): 145 if current_user is not None: 146 host.user = current_user 147 return False 148 return result.exit_status == 0 149 150 151 # TODO(garnold) Remove the 'serials' argument once all clients are made to 152 # not use it. 153 def _initialize(self, hostname='localhost', serials=None, 154 adb_serial=None, fastboot_serial=None, 155 device_hostname=None, teststation=None, *args, **dargs): 156 """Initialize an ADB Host. 157 158 This will create an ADB Host. Hostname should always refer to the 159 test station connected to an Android DUT. This will be the DUT 160 to test with. If there are multiple, serial must be specified or an 161 exception will be raised. If device_hostname is supplied then all 162 ADB commands will run over TCP/IP. 163 164 @param hostname: Hostname of the machine running ADB. 165 @param serials: DEPRECATED (to be removed) 166 @param adb_serial: An ADB device serial. If None, assume a single 167 device is attached (and fail otherwise). 168 @param fastboot_serial: A fastboot device serial. If None, defaults to 169 the ADB serial (or assumes a single device if 170 the latter is None). 171 @param device_hostname: Hostname or IP of the android device we want to 172 interact with. If supplied all ADB interactions 173 run over TCP/IP. 174 @param teststation: The teststation object ADBHost should use. 175 """ 176 # Sets up the is_client_install_supported field. 177 super(ADBHost, self)._initialize(hostname=hostname, 178 is_client_install_supported=False, 179 *args, **dargs) 180 if device_hostname and (adb_serial or fastboot_serial): 181 raise error.AutoservError( 182 'TCP/IP and USB modes are mutually exclusive') 183 184 185 self.tmp_dirs = [] 186 self._device_hostname = device_hostname 187 self._use_tcpip = False 188 # TODO (sbasi/kevcheng): Once the teststation host is committed, 189 # refactor the serial retrieval. 190 adb_serial = adb_serial or self.host_attributes.get('serials', None) 191 self.adb_serial = adb_serial 192 self.fastboot_serial = fastboot_serial or adb_serial 193 self.teststation = (teststation if teststation 194 else teststation_host.create_teststationhost(hostname=hostname)) 195 196 msg ='Initializing ADB device on host: %s' % hostname 197 if self._device_hostname: 198 msg += ', device hostname: %s' % self._device_hostname 199 if self.adb_serial: 200 msg += ', ADB serial: %s' % self.adb_serial 201 if self.fastboot_serial: 202 msg += ', fastboot serial: %s' % self.fastboot_serial 203 logging.debug(msg) 204 205 # Try resetting the ADB daemon on the device, however if we are 206 # creating the host to do a repair job, the device maybe inaccesible 207 # via ADB. 208 try: 209 self._reset_adbd_connection() 210 except (error.AutotestHostRunError, error.AutoservRunError) as e: 211 logging.error('Unable to reset the device adb daemon connection: ' 212 '%s.', e) 213 self._os_type = None 214 215 216 def _connect_over_tcpip_as_needed(self): 217 """Connect to the ADB device over TCP/IP if so configured.""" 218 if not self._device_hostname: 219 return 220 logging.debug('Connecting to device over TCP/IP') 221 if self._device_hostname == self.adb_serial: 222 # We previously had a connection to this device, restart the ADB 223 # server. 224 self.adb_run('kill-server') 225 # Ensure that connection commands don't run over TCP/IP. 226 self._use_tcpip = False 227 self.adb_run('tcpip 5555', timeout=10, ignore_timeout=True) 228 time.sleep(2) 229 try: 230 self.adb_run('connect %s' % self._device_hostname) 231 except (error.AutoservRunError, error.CmdError) as e: 232 raise error.AutoservError('Failed to connect via TCP/IP: %s' % e) 233 # Allow ADB a bit of time after connecting before interacting with the 234 # device. 235 time.sleep(5) 236 # Switch back to using TCP/IP. 237 self._use_tcpip = True 238 239 240 def _restart_adbd_with_root_permissions(self): 241 """Restarts the adb daemon with root permissions.""" 242 self.adb_run('root') 243 # TODO(ralphnathan): Remove this sleep once b/19749057 is resolved. 244 time.sleep(1) 245 self.adb_run('wait-for-device') 246 247 248 def _reset_adbd_connection(self): 249 """Resets adbd connection to the device after a reboot/initialization""" 250 self._restart_adbd_with_root_permissions() 251 self._connect_over_tcpip_as_needed() 252 253 254 # pylint: disable=missing-docstring 255 def adb_run(self, command, **kwargs): 256 """Runs an adb command. 257 258 This command will launch on the test station. 259 260 Refer to _device_run method for docstring for parameters. 261 """ 262 return self._device_run(ADB_CMD, command, **kwargs) 263 264 265 # pylint: disable=missing-docstring 266 def fastboot_run(self, command, **kwargs): 267 """Runs an fastboot command. 268 269 This command will launch on the test station. 270 271 Refer to _device_run method for docstring for parameters. 272 """ 273 return self._device_run(FASTBOOT_CMD, command, **kwargs) 274 275 276 def _device_run(self, function, command, shell=False, 277 timeout=3600, ignore_status=False, ignore_timeout=False, 278 stdout=utils.TEE_TO_LOGS, stderr=utils.TEE_TO_LOGS, 279 connect_timeout=30, options='', stdin=None, verbose=True, 280 require_sudo=False, args=()): 281 """Runs a command named `function` on the test station. 282 283 This command will launch on the test station. 284 285 @param command: Command to run. 286 @param shell: If true the command runs in the adb shell otherwise if 287 False it will be passed directly to adb. For example 288 reboot with shell=False will call 'adb reboot'. This 289 option only applies to function adb. 290 @param timeout: Time limit in seconds before attempting to 291 kill the running process. The run() function 292 will take a few seconds longer than 'timeout' 293 to complete if it has to kill the process. 294 @param ignore_status: Do not raise an exception, no matter 295 what the exit code of the command is. 296 @param ignore_timeout: Bool True if command timeouts should be 297 ignored. Will return None on command timeout. 298 @param stdout: Redirect stdout. 299 @param stderr: Redirect stderr. 300 @param connect_timeout: Connection timeout (in seconds) 301 @param options: String with additional ssh command options 302 @param stdin: Stdin to pass (a string) to the executed command 303 @param require_sudo: True to require sudo to run the command. Default is 304 False. 305 @param args: Sequence of strings to pass as arguments to command by 306 quoting them in " and escaping their contents if 307 necessary. 308 309 @returns a CMDResult object. 310 """ 311 if function == ADB_CMD: 312 serial = self.adb_serial 313 elif function == FASTBOOT_CMD: 314 serial = self.fastboot_serial 315 else: 316 raise NotImplementedError('Mode %s is not supported' % function) 317 318 if function != ADB_CMD and shell: 319 raise error.CmdError('shell option is only applicable to `adb`.') 320 321 cmd = '%s%s ' % ('sudo -n ' if require_sudo else '', function) 322 323 if serial: 324 cmd += '-s %s ' % serial 325 elif self._use_tcpip: 326 cmd += '-s %s:5555 ' % self._device_hostname 327 328 if shell: 329 cmd += '%s ' % SHELL_CMD 330 cmd += command 331 332 if verbose: 333 logging.debug('Command: %s', cmd) 334 335 return self.teststation.run(cmd, timeout=timeout, 336 ignore_status=ignore_status, 337 ignore_timeout=ignore_timeout, stdout_tee=stdout, 338 stderr_tee=stderr, options=options, stdin=stdin, 339 connect_timeout=connect_timeout, args=args) 340 341 342 def get_board_name(self): 343 """Get the name of the board, e.g., shamu, dragonboard etc. 344 """ 345 return self.run_output('getprop %s' % BOARD_FILE) 346 347 348 @label_decorator() 349 def get_board(self): 350 """Determine the correct board label for the device. 351 352 @returns a string representing this device's board. 353 """ 354 board = self.get_board_name() 355 board_os = self.get_os_type() 356 return constants.BOARD_PREFIX + '-'.join([board_os, board]) 357 358 359 def job_start(self): 360 """ 361 Disable log collection on adb_hosts. 362 363 TODO(sbasi): crbug.com/305427 364 """ 365 366 367 def run(self, command, timeout=3600, ignore_status=False, 368 ignore_timeout=False, stdout_tee=utils.TEE_TO_LOGS, 369 stderr_tee=utils.TEE_TO_LOGS, connect_timeout=30, options='', 370 stdin=None, verbose=True, args=()): 371 """Run a command on the adb device. 372 373 The command given will be ran directly on the adb device; for example 374 'ls' will be ran as: 'abd shell ls' 375 376 @param command: The command line string. 377 @param timeout: Time limit in seconds before attempting to 378 kill the running process. The run() function 379 will take a few seconds longer than 'timeout' 380 to complete if it has to kill the process. 381 @param ignore_status: Do not raise an exception, no matter 382 what the exit code of the command is. 383 @param ignore_timeout: Bool True if command timeouts should be 384 ignored. Will return None on command timeout. 385 @param stdout_tee: Redirect stdout. 386 @param stderr_tee: Redirect stderr. 387 @param connect_timeout: Connection timeout (in seconds). 388 @param options: String with additional ssh command options. 389 @param stdin: Stdin to pass (a string) to the executed command 390 @param args: Sequence of strings to pass as arguments to command by 391 quoting them in " and escaping their contents if 392 necessary. 393 394 @returns A CMDResult object or None if the call timed out and 395 ignore_timeout is True. 396 397 @raises AutoservRunError: If the command failed. 398 @raises AutoservSSHTimeout: Ssh connection has timed out. 399 """ 400 command = ('"%s; echo %s:\$?"' % 401 (utils.sh_escape(command), CMD_OUTPUT_PREFIX)) 402 result = self.adb_run( 403 command, shell=True, timeout=timeout, 404 ignore_status=ignore_status, ignore_timeout=ignore_timeout, 405 stdout=stdout_tee, stderr=stderr_tee, 406 connect_timeout=connect_timeout, options=options, stdin=stdin, 407 verbose=verbose, args=args) 408 if not result: 409 # In case of timeouts. 410 return None 411 412 parse_output = re.match(CMD_OUTPUT_REGEX, result.stdout) 413 if not parse_output and not ignore_status: 414 raise error.AutoservRunError( 415 'Failed to parse the exit code for command: %s' % 416 command, result) 417 elif parse_output: 418 result.stdout = parse_output.group('OUTPUT') 419 result.exit_status = int(parse_output.group('EXIT_CODE')) 420 if result.exit_status != 0 and not ignore_status: 421 raise error.AutoservRunError(command, result) 422 return result 423 424 425 def wait_up(self, timeout=DEFAULT_WAIT_UP_TIME_SECONDS, command=ADB_CMD): 426 """Wait until the remote host is up or the timeout expires. 427 428 Overrides wait_down from AbstractSSHHost. 429 430 @param timeout: Time limit in seconds before returning even if the host 431 is not up. 432 @param command: The command used to test if a device is up, i.e., 433 accessible by the given command. Default is set to `adb`. 434 435 @returns True if the host was found to be up before the timeout expires, 436 False otherwise. 437 """ 438 @retry.retry(error.TimeoutException, timeout_min=timeout/60.0, 439 delay_sec=1) 440 def _wait_up(): 441 if not self.is_up(command=command): 442 raise error.TimeoutException('Device is still down.') 443 return True 444 445 try: 446 _wait_up() 447 logging.debug('Host %s is now up, and can be accessed by %s.', 448 self.hostname, command) 449 return True 450 except error.TimeoutException: 451 logging.debug('Host %s is still down after waiting %d seconds', 452 self.hostname, timeout) 453 return False 454 455 456 def wait_down(self, timeout=DEFAULT_WAIT_DOWN_TIME_SECONDS, 457 warning_timer=None, old_boot_id=None, command=ADB_CMD): 458 """Wait till the host goes down, i.e., not accessible by given command. 459 460 Overrides wait_down from AbstractSSHHost. 461 462 @param timeout: Time in seconds to wait for the host to go down. 463 @param warning_timer: Time limit in seconds that will generate 464 a warning if the host is not down yet. 465 Currently ignored. 466 @param old_boot_id: Not applicable for adb_host. 467 @param command: `adb`, test if the device can be accessed by adb 468 command, or `fastboot`, test if the device can be accessed by 469 fastboot command. Default is set to `adb`. 470 471 @returns True if the device goes down before the timeout, False 472 otherwise. 473 """ 474 @retry.retry(error.TimeoutException, timeout_min=timeout/60.0, 475 delay_sec=1) 476 def _wait_down(): 477 if self.is_up(command=command): 478 raise error.TimeoutException('Device is still up.') 479 return True 480 481 try: 482 _wait_down() 483 logging.debug('Host %s is now down', self.hostname) 484 return True 485 except error.TimeoutException: 486 logging.debug('Host %s is still up after waiting %d seconds', 487 self.hostname, timeout) 488 return False 489 490 491 def reboot(self): 492 """Reboot the android device via adb. 493 494 @raises AutoservRebootError if reboot failed. 495 """ 496 # Not calling super.reboot() as we want to reboot the ADB device not 497 # the test station we are running ADB on. 498 self.adb_run('reboot', timeout=10, ignore_timeout=True) 499 if not self.wait_down(): 500 raise error.AutoservRebootError( 501 'ADB Device is still up after reboot') 502 if not self.wait_up(): 503 raise error.AutoservRebootError( 504 'ADB Device failed to return from reboot.') 505 self._reset_adbd_connection() 506 507 508 def remount(self): 509 """Remounts paritions on the device read-write. 510 511 Specifically, the /system, /vendor (if present) and /oem (if present) 512 partitions on the device are remounted read-write. 513 """ 514 self.adb_run('remount') 515 516 517 @staticmethod 518 def parse_device_serials(devices_output): 519 """Return a list of parsed serials from the output. 520 521 @param devices_output: Output from either an adb or fastboot command. 522 523 @returns List of device serials 524 """ 525 devices = [] 526 for line in devices_output.splitlines(): 527 match = re.search(DEVICE_FINDER_REGEX, line) 528 if match: 529 serial = match.group('SERIAL') 530 if serial == DEVICE_NO_SERIAL_MSG or re.match(r'^\?+$', serial): 531 serial = DEVICE_NO_SERIAL_TAG 532 logging.debug('Found Device: %s', serial) 533 devices.append(serial) 534 return devices 535 536 537 def _get_devices(self, use_adb): 538 """Get a list of devices currently attached to the test station. 539 540 @params use_adb: True to get adb accessible devices. Set to False to 541 get fastboot accessible devices. 542 543 @returns a list of devices attached to the test station. 544 """ 545 if use_adb: 546 result = self.adb_run('devices') 547 else: 548 result = self.fastboot_run('devices') 549 return self.parse_device_serials(result.stdout) 550 551 552 def adb_devices(self): 553 """Get a list of devices currently attached to the test station and 554 accessible with the adb command.""" 555 devices = self._get_devices(use_adb=True) 556 if self.adb_serial is None and len(devices) > 1: 557 raise error.AutoservError( 558 'Not given ADB serial but multiple devices detected') 559 return devices 560 561 562 def fastboot_devices(self): 563 """Get a list of devices currently attached to the test station and 564 accessible by fastboot command. 565 """ 566 devices = self._get_devices(use_adb=False) 567 if self.fastboot_serial is None and len(devices) > 1: 568 raise error.AutoservError( 569 'Not given fastboot serial but multiple devices detected') 570 return devices 571 572 573 def is_up(self, timeout=0, command=ADB_CMD): 574 """Determine if the specified adb device is up with expected mode. 575 576 @param timeout: Not currently used. 577 @param command: `adb`, the device can be accessed by adb command, 578 or `fastboot`, the device can be accessed by fastboot command. 579 Default is set to `adb`. 580 581 @returns True if the device is detectable by given command, False 582 otherwise. 583 584 """ 585 if command == ADB_CMD: 586 devices = self.adb_devices() 587 serial = self.adb_serial 588 # ADB has a device state, if the device is not online, no 589 # subsequent ADB command will complete. 590 if len(devices) == 0 or not self.is_device_ready(): 591 logging.debug('Waiting for device to enter the ready state.') 592 return False 593 elif command == FASTBOOT_CMD: 594 devices = self.fastboot_devices() 595 serial = self.fastboot_serial 596 else: 597 raise NotImplementedError('Mode %s is not supported' % command) 598 599 return bool(devices and (not serial or serial in devices)) 600 601 602 def close(self): 603 """Close the ADBHost object. 604 605 Called as the test ends. Will return the device to USB mode and kill 606 the ADB server. 607 """ 608 if self._use_tcpip: 609 # Return the device to usb mode. 610 self.adb_run('usb') 611 # TODO(sbasi) Originally, we would kill the server after each test to 612 # reduce the opportunity for bad server state to hang around. 613 # Unfortunately, there is a period of time after each kill during which 614 # the Android device becomes unusable, and if we start the next test 615 # too quickly, we'll get an error complaining about no ADB device 616 # attached. 617 #self.adb_run('kill-server') 618 # |close| the associated teststation as well. 619 self.teststation.close() 620 return super(ADBHost, self).close() 621 622 623 def syslog(self, message, tag='autotest'): 624 """Logs a message to syslog on the device. 625 626 @param message String message to log into syslog 627 @param tag String tag prefix for syslog 628 629 """ 630 self.run('log -t "%s" "%s"' % (tag, message)) 631 632 633 def get_autodir(self): 634 """Return the directory to install autotest for client side tests.""" 635 return '/data/autotest' 636 637 638 def is_device_ready(self): 639 """Return the if the device is ready for ADB commands.""" 640 dev_state = self.adb_run('get-state').stdout.strip() 641 logging.debug('Current device state: %s', dev_state) 642 return dev_state == 'device' 643 644 645 def verify_connectivity(self): 646 """Verify we can connect to the device.""" 647 if not self.is_device_ready(): 648 raise error.AutoservHostError('device state is not in the ' 649 '\'device\' state.') 650 651 652 def verify_software(self): 653 """Verify working software on an adb_host. 654 655 TODO (crbug.com/532222): Actually implement this method. 656 """ 657 # Check if adb and fastboot are present. 658 self.teststation.run('which adb') 659 self.teststation.run('which fastboot') 660 self.teststation.run('which unzip') 661 662 663 def verify_job_repo_url(self, tag=''): 664 """Make sure job_repo_url of this host is valid. 665 666 TODO (crbug.com/532223): Actually implement this method. 667 668 @param tag: The tag from the server job, in the format 669 <job_id>-<user>/<hostname>, or <hostless> for a server job. 670 """ 671 return 672 673 674 def repair(self): 675 """Attempt to get the DUT to pass `self.verify()`.""" 676 try: 677 self.ensure_adb_mode(timeout=30) 678 return 679 except error.AutoservError as e: 680 logging.error(e) 681 logging.debug('Verifying the device is accessible via fastboot.') 682 self.ensure_bootloader_mode() 683 if not self.job.run_test( 684 'provision_AndroidUpdate', host=self, value=None, 685 force=True, repair=True): 686 raise error.AutoservRepairTotalFailure( 687 'Unable to repair the device.') 688 689 690 def send_file(self, source, dest, delete_dest=False, 691 preserve_symlinks=False): 692 """Copy files from the drone to the device. 693 694 Just a note, there is the possibility the test station is localhost 695 which makes some of these steps redundant (e.g. creating tmp dir) but 696 that scenario will undoubtedly be a development scenario (test station 697 is also the moblab) and not the typical live test running scenario so 698 the redundancy I think is harmless. 699 700 @param source: The file/directory on the drone to send to the device. 701 @param dest: The destination path on the device to copy to. 702 @param delete_dest: A flag set to choose whether or not to delete 703 dest on the device if it exists. 704 @param preserve_symlinks: Controls if symlinks on the source will be 705 copied as such on the destination or 706 transformed into the referenced 707 file/directory. 708 """ 709 # If we need to preserve symlinks, let's check if the source is a 710 # symlink itself and if so, just create it on the device. 711 if preserve_symlinks: 712 symlink_target = None 713 try: 714 symlink_target = os.readlink(source) 715 except OSError: 716 # Guess it's not a symlink. 717 pass 718 719 if symlink_target is not None: 720 # Once we create the symlink, let's get out of here. 721 self.run('ln -s %s %s' % (symlink_target, dest)) 722 return 723 724 # Stage the files on the test station. 725 tmp_dir = self.teststation.get_tmp_dir() 726 src_path = os.path.join(tmp_dir, os.path.basename(dest)) 727 # Now copy the file over to the test station so you can reference the 728 # file in the push command. 729 self.teststation.send_file(source, src_path, 730 preserve_symlinks=preserve_symlinks) 731 732 if delete_dest: 733 self.run('rm -rf %s' % dest) 734 735 self.adb_run('push %s %s' % (src_path, dest)) 736 737 # Cleanup the test station. 738 try: 739 self.teststation.run('rm -rf %s' % tmp_dir) 740 except (error.AutoservRunError, error.AutoservSSHTimeout) as e: 741 logging.warn('failed to remove dir %s: %s', tmp_dir, e) 742 743 744 def _get_file_info(self, dest): 745 """Get permission and possible symlink info about file on the device. 746 747 These files are on the device so we only have shell commands (via adb) 748 to get the info we want. We'll use 'ls' to get it all. 749 750 @param dest: File to get info about. 751 752 @returns a dict of the file permissions and symlink. 753 """ 754 # Grab file info. 755 file_info = self.run_output('ls -l %s' % dest) 756 symlink = None 757 perms = 0 758 match = re.match(FILE_INFO_REGEX, file_info) 759 if match: 760 # Check if it's a symlink and grab the linked dest if it is. 761 if match.group('TYPE') == 'l': 762 symlink_match = re.match(FILE_SYMLINK_REGEX, file_info) 763 if symlink_match: 764 symlink = symlink_match.group('SYMLINK') 765 766 # Set the perms. 767 for perm, perm_flag in zip(match.group('PERMS'), FILE_PERMS_FLAGS): 768 if perm != '-': 769 perms |= perm_flag 770 771 return {'perms': perms, 772 'symlink': symlink} 773 774 775 def get_file(self, source, dest, delete_dest=False, preserve_perm=True, 776 preserve_symlinks=False): 777 """Copy files from the device to the drone. 778 779 Just a note, there is the possibility the test station is localhost 780 which makes some of these steps redundant (e.g. creating tmp dir) but 781 that scenario will undoubtedly be a development scenario (test station 782 is also the moblab) and not the typical live test running scenario so 783 the redundancy I think is harmless. 784 785 @param source: The file/directory on the device to copy back to the 786 drone. 787 @param dest: The destination path on the drone to copy to. 788 @param delete_dest: A flag set to choose whether or not to delete 789 dest on the drone if it exists. 790 @param preserve_perm: Tells get_file() to try to preserve the sources 791 permissions on files and dirs. 792 @param preserve_symlinks: Try to preserve symlinks instead of 793 transforming them into files/dirs on copy. 794 """ 795 # Stage the files on the test station. 796 tmp_dir = self.teststation.get_tmp_dir() 797 dest_path = os.path.join(tmp_dir, os.path.basename(source)) 798 799 if delete_dest: 800 self.teststation.run('rm -rf %s' % dest) 801 802 source_info = {} 803 if preserve_symlinks or preserve_perm: 804 source_info = self._get_file_info(source) 805 806 # If we want to preserve symlinks, just create it here, otherwise pull 807 # the file off the device. 808 if preserve_symlinks and source_info['symlink']: 809 os.symlink(source_info['symlink'], dest) 810 else: 811 self.adb_run('pull %s %s' % (source, dest_path)) 812 813 # Copy over the file from the test station and clean up. 814 self.teststation.get_file(dest_path, dest) 815 try: 816 self.teststation.run('rm -rf %s' % tmp_dir) 817 except (error.AutoservRunError, error.AutoservSSHTimeout) as e: 818 logging.warn('failed to remove dir %s: %s', tmp_dir, e) 819 820 if preserve_perm: 821 os.chmod(dest, source_info['perms']) 822 823 824 def get_release_version(self): 825 """Get the release version from the RELEASE_FILE on the device. 826 827 @returns The release string in the RELEASE_FILE. 828 829 """ 830 return self.run_output('getprop %s' % RELEASE_FILE) 831 832 833 def get_tmp_dir(self, parent=''): 834 """Return a suitable temporary directory on the device. 835 836 We ensure this is a subdirectory of /data/local/tmp. 837 838 @param parent: Parent directory of the returned tmp dir. 839 840 @returns a path to the temp directory on the host. 841 """ 842 # TODO(kevcheng): Refactor the cleanup of tmp dir to be inherited 843 # from the parent. 844 if not parent.startswith(TMP_DIR): 845 parent = os.path.join(TMP_DIR, parent.lstrip(os.path.sep)) 846 self.run('mkdir -p %s' % parent) 847 tmp_dir = self.run_output('mktemp -d -p %s' % parent) 848 self.tmp_dirs.append(tmp_dir) 849 return tmp_dir 850 851 852 def get_platform(self): 853 """Determine the correct platform label for this host. 854 855 TODO (crbug.com/536250): Figure out what we want to do for adb_host's 856 get_platform. 857 858 @returns a string representing this host's platform. 859 """ 860 return 'adb' 861 862 863 def get_os_type(self): 864 """Get the OS type of the DUT, e.g., android or brillo. 865 """ 866 if not self._os_type: 867 if self.run_output('getprop ro.product.brand') == 'Brillo': 868 self._os_type = OS_TYPE_BRILLO 869 else: 870 self._os_type = OS_TYPE_ANDROID 871 872 return self._os_type 873 874 875 def _forward(self, reverse, args): 876 """Execute a forwarding command. 877 878 @param reverse: Whether this is reverse forwarding (Boolean). 879 @param args: List of command arguments. 880 """ 881 cmd = '%s %s' % ('reverse' if reverse else 'forward', ' '.join(args)) 882 self.adb_run(cmd) 883 884 885 def add_forwarding(self, src, dst, reverse=False, rebind=True): 886 """Forward a port between the ADB host and device. 887 888 Port specifications are any strings accepted as such by ADB, for 889 example 'tcp:8080'. 890 891 @param src: Port specification to forward from. 892 @param dst: Port specification to forward to. 893 @param reverse: Do reverse forwarding from device to host (Boolean). 894 @param rebind: Allow rebinding an already bound port (Boolean). 895 """ 896 args = [] 897 if not rebind: 898 args.append('--no-rebind') 899 args += [src, dst] 900 self._forward(reverse, args) 901 902 903 def remove_forwarding(self, src=None, reverse=False): 904 """Removes forwarding on port. 905 906 @param src: Port specification, or None to remove all forwarding. 907 @param reverse: Whether this is reverse forwarding (Boolean). 908 """ 909 args = [] 910 if src is None: 911 args.append('--remove-all') 912 else: 913 args += ['--remove', src] 914 self._forward(reverse, args) 915 916 917 def rpc_port_forward(self, port, local_port): 918 """ 919 Forwards a port securely through a tunnel process from the server 920 to the DUT for RPC server connection. 921 Add a 'ADB forward' rule to forward the RPC packets from the AdbHost 922 to the DUT. 923 924 @param port: remote port on the DUT. 925 @param local_port: local forwarding port. 926 927 @return: the tunnel process. 928 """ 929 self.add_forwarding('tcp:%s' % port, 'tcp:%s' % port) 930 return super(ADBHost, self).rpc_port_forward(port, local_port) 931 932 933 def rpc_port_disconnect(self, tunnel_proc, port): 934 """ 935 Disconnects a previously forwarded port from the server to the DUT for 936 RPC server connection. 937 Remove the previously added 'ADB forward' rule to forward the RPC 938 packets from the AdbHost to the DUT. 939 940 @param tunnel_proc: the original tunnel process returned from 941 |rpc_port_forward|. 942 @param port: remote port on the DUT. 943 944 """ 945 self.remove_forwarding('tcp:%s' % port) 946 super(ADBHost, self).rpc_port_disconnect(tunnel_proc, port) 947 948 949 def ensure_bootloader_mode(self): 950 """Ensure the device is in bootloader mode. 951 952 @raise: error.AutoservError if the device failed to reboot into 953 bootloader mode. 954 """ 955 if self.is_up(command=FASTBOOT_CMD): 956 return 957 self.adb_run('reboot bootloader') 958 if not self.wait_up(command=FASTBOOT_CMD): 959 raise error.AutoservError( 960 'The device failed to reboot into bootloader mode.') 961 962 963 def ensure_adb_mode(self, timeout=DEFAULT_WAIT_UP_TIME_SECONDS): 964 """Ensure the device is up and can be accessed by adb command. 965 966 @param timeout: Time limit in seconds before returning even if the host 967 is not up. 968 969 @raise: error.AutoservError if the device failed to reboot into 970 adb mode. 971 """ 972 if self.is_up(): 973 return 974 self.fastboot_run('reboot') 975 if not self.wait_up(timeout=timeout): 976 raise error.AutoservError( 977 'The device failed to reboot into adb mode.') 978 self._reset_adbd_connection() 979 980 981 @classmethod 982 def _get_build_info_from_build_url(cls, build_url): 983 """Get the Android build information from the build url. 984 985 @param build_url: The url to use for downloading Android artifacts. 986 pattern: http://$devserver:###/static/branch/target/build_id 987 988 @return: A dictionary of build information, including keys: board, 989 branch, target, build_id. 990 @raise AndroidInstallError: If failed to parse build_url. 991 """ 992 if not build_url: 993 raise AndroidInstallError('Need build_url to download image files.') 994 995 try: 996 match = re.match(DEVSERVER_URL_REGEX, build_url) 997 return {'board': match.group('BOARD'), 998 'branch': match.group('BRANCH'), 999 'target': ('%s-%s' % (match.group('BOARD'), 1000 match.group('BUILD_TYPE'))), 1001 'build_id': match.group('BUILD_ID')} 1002 except (AttributeError, IndexError, ValueError) as e: 1003 raise AndroidInstallError( 1004 'Failed to parse build url: %s\nError: %s' % (build_url, e)) 1005 1006 1007 @retry.retry(error.AutoservRunError, timeout_min=10) 1008 def _download_file(self, build_url, file, dest_dir): 1009 """Download the given file from the build url. 1010 1011 @param build_url: The url to use for downloading Android artifacts. 1012 pattern: http://$devserver:###/static/branch/target/build_id 1013 @param file: Name of the file to be downloaded, e.g., boot.img. 1014 @param dest_dir: Destination folder for the file to be downloaded to. 1015 """ 1016 src_url = os.path.join(build_url, file) 1017 dest_file = os.path.join(dest_dir, file) 1018 try: 1019 self.teststation.run('wget -q -O "%s" "%s"' % (dest_file, src_url)) 1020 except: 1021 # Delete the destination file if download failed. 1022 self.teststation.run('rm -f "%s"' % dest_file) 1023 raise 1024 1025 1026 def stage_android_image_files(self, build_url): 1027 """Download required image files from the given build_url to a local 1028 directory in the machine runs fastboot command. 1029 1030 @param build_url: The url to use for downloading Android artifacts. 1031 pattern: http://$devserver:###/static/branch/target/build_id 1032 1033 @return: Path to the directory contains image files. 1034 """ 1035 build_info = self._get_build_info_from_build_url(build_url) 1036 1037 zipped_image_file = ANDROID_IMAGE_FILE_FMT % build_info 1038 image_dir = self.teststation.get_tmp_dir() 1039 image_files = [zipped_image_file] + ANDROID_STANDALONE_IMAGES 1040 1041 try: 1042 for image_file in image_files: 1043 self._download_file(build_url, image_file, image_dir) 1044 1045 self.teststation.run('unzip "%s/%s" -x -d "%s"' % 1046 (image_dir, zipped_image_file, image_dir)) 1047 1048 return image_dir 1049 except: 1050 self.teststation.run('rm -rf %s' % image_dir) 1051 raise 1052 1053 1054 def stage_brillo_image_files(self, build_url): 1055 """Download required brillo image files from the given build_url to a 1056 local directory in the machine runs fastboot command. 1057 1058 @param build_url: The url to use for downloading Android artifacts. 1059 pattern: http://$devserver:###/static/branch/target/build_id 1060 1061 @return: Path to the directory contains image files. 1062 """ 1063 build_info = self._get_build_info_from_build_url(build_url) 1064 1065 zipped_image_file = ANDROID_IMAGE_FILE_FMT % build_info 1066 vendor_partitions_file = BRILLO_VENDOR_PARTITIONS_FILE_FMT % build_info 1067 image_dir = self.teststation.get_tmp_dir() 1068 image_files = [zipped_image_file, vendor_partitions_file] 1069 1070 try: 1071 for image_file in image_files: 1072 self._download_file(build_url, image_file, image_dir) 1073 1074 self.teststation.run('unzip "%s/%s" -x -d "%s"' % 1075 (image_dir, zipped_image_file, image_dir)) 1076 self.teststation.run('unzip "%s/%s" -x -d "%s"' % 1077 (image_dir, vendor_partitions_file, 1078 os.path.join(image_dir, 'vendor'))) 1079 return image_dir 1080 except: 1081 self.teststation.run('rm -rf %s' % image_dir) 1082 raise 1083 1084 1085 def stage_build_for_install(self, build_name, os_type=None): 1086 """Stage a build on a devserver and return the build_url and devserver. 1087 1088 @param build_name: a name like git-master/shamu-userdebug/2040953 1089 1090 @returns a tuple with an update URL like: 1091 http://172.22.50.122:8080/git-master/shamu-userdebug/2040953 1092 and the devserver instance. 1093 """ 1094 os_type = os_type or self.get_os_type() 1095 logging.info('Staging build for installation: %s', build_name) 1096 devserver = dev_server.AndroidBuildServer.resolve(build_name, 1097 self.hostname) 1098 build_name = devserver.translate(build_name) 1099 branch, target, build_id = utils.parse_android_build(build_name) 1100 is_brillo = os_type == OS_TYPE_BRILLO 1101 devserver.trigger_download(target, build_id, branch, is_brillo, 1102 synchronous=False) 1103 return '%s/static/%s' % (devserver.url(), build_name), devserver 1104 1105 1106 def install_android(self, build_url, build_local_path=None, wipe=True, 1107 flash_all=False): 1108 """Install the Android DUT. 1109 1110 Following are the steps used here to provision an android device: 1111 1. If build_local_path is not set, download the image zip file, e.g., 1112 shamu-img-2284311.zip, unzip it. 1113 2. Run fastboot to install following artifacts: 1114 bootloader, radio, boot, system, vendor(only if exists) 1115 1116 Repair is not supported for Android devices yet. 1117 1118 @param build_url: The url to use for downloading Android artifacts. 1119 pattern: http://$devserver:###/static/$build 1120 @param build_local_path: The path to a local folder that contains the 1121 image files needed to provision the device. Note that the folder 1122 is in the machine running adb command, rather than the drone. 1123 @param wipe: If true, userdata will be wiped before flashing. 1124 @param flash_all: If True, all img files found in img_path will be 1125 flashed. Otherwise, only boot and system are flashed. 1126 1127 @raises AndroidInstallError if any error occurs. 1128 """ 1129 # If the build is not staged in local server yet, clean up the temp 1130 # folder used to store image files after the provision is completed. 1131 delete_build_folder = bool(not build_local_path) 1132 1133 try: 1134 # Download image files needed for provision to a local directory. 1135 if not build_local_path: 1136 build_local_path = self.stage_android_image_files(build_url) 1137 1138 # Device needs to be in bootloader mode for flashing. 1139 self.ensure_bootloader_mode() 1140 1141 if wipe: 1142 self.fastboot_run('-w') 1143 1144 # Get all *.img file in the build_local_path. 1145 list_file_cmd = 'ls -d %s' % os.path.join(build_local_path, '*.img') 1146 image_files = self.teststation.run( 1147 list_file_cmd).stdout.strip().split() 1148 images = dict([(os.path.basename(f), f) for f in image_files]) 1149 for image, image_file in images.items(): 1150 if image not in ANDROID_IMAGES: 1151 continue 1152 logging.info('Flashing %s...', image_file) 1153 self.fastboot_run('flash %s %s' % (image[:-4], image_file)) 1154 if image == ANDROID_BOOTLOADER: 1155 self.fastboot_run('reboot-bootloader') 1156 self.wait_up(command=FASTBOOT_CMD) 1157 except Exception as e: 1158 logging.error('Install Android build failed with error: %s', e) 1159 # Re-raise the exception with type of AndroidInstallError. 1160 raise AndroidInstallError, sys.exc_info()[1], sys.exc_info()[2] 1161 finally: 1162 if delete_build_folder: 1163 self.teststation.run('rm -rf %s' % build_local_path) 1164 timeout = (WAIT_UP_AFTER_WIPE_TIME_SECONDS if wipe else 1165 DEFAULT_WAIT_UP_TIME_SECONDS) 1166 self.ensure_adb_mode(timeout=timeout) 1167 logging.info('Successfully installed Android build staged at %s.', 1168 build_url) 1169 1170 1171 def install_brillo(self, build_url, build_local_path=None): 1172 """Install the Brillo DUT. 1173 1174 Following are the steps used here to provision an android device: 1175 1. If build_local_path is not set, download the image zip file, e.g., 1176 dragonboard-img-123456.zip, unzip it. And download the vendor 1177 partition zip file, e.g., dragonboard-vendor_partitions-123456.zip, 1178 unzip it to vendor folder. 1179 2. Run provision_device script to install OS images and vendor 1180 partitions. 1181 1182 @param build_url: The url to use for downloading Android artifacts. 1183 pattern: http://$devserver:###/static/$build 1184 @param build_local_path: The path to a local folder that contains the 1185 image files needed to provision the device. Note that the folder 1186 is in the machine running adb command, rather than the drone. 1187 1188 @raises AndroidInstallError if any error occurs. 1189 """ 1190 # If the build is not staged in local server yet, clean up the temp 1191 # folder used to store image files after the provision is completed. 1192 delete_build_folder = bool(not build_local_path) 1193 1194 try: 1195 # Download image files needed for provision to a local directory. 1196 if not build_local_path: 1197 build_local_path = self.stage_brillo_image_files(build_url) 1198 1199 # Device needs to be in bootloader mode for flashing. 1200 self.ensure_bootloader_mode() 1201 1202 # Run provision_device command to install image files and vendor 1203 # partitions. 1204 vendor_partition_dir = os.path.join(build_local_path, 'vendor') 1205 cmd = (BRILLO_PROVISION_CMD % 1206 {'os_image_dir': build_local_path, 1207 'vendor_partition_dir': vendor_partition_dir}) 1208 self.teststation.run(cmd) 1209 except Exception as e: 1210 logging.error('Install Brillo build failed with error: %s', e) 1211 # Re-raise the exception with type of AndroidInstallError. 1212 raise AndroidInstallError, sys.exc_info()[1], sys.exc_info()[2] 1213 finally: 1214 if delete_build_folder: 1215 self.teststation.run('rm -rf %s' % build_local_path) 1216 self.ensure_adb_mode() 1217 logging.info('Successfully installed Android build staged at %s.', 1218 build_url) 1219 1220 1221 def machine_install(self, build_url=None, build_local_path=None, wipe=True, 1222 flash_all=False, os_type=None): 1223 """Install the DUT. 1224 1225 @param build_url: The url to use for downloading Android artifacts. 1226 pattern: http://$devserver:###/static/$build. If build_url is 1227 set to None, the code may try _parser.options.image to do the 1228 installation. If none of them is set, machine_install will fail. 1229 @param build_local_path: The path to a local directory that contains the 1230 image files needed to provision the device. 1231 @param wipe: If true, userdata will be wiped before flashing. 1232 @param flash_all: If True, all img files found in img_path will be 1233 flashed. Otherwise, only boot and system are flashed. 1234 1235 @returns Name of the image installed. 1236 """ 1237 os_type = os_type or self.get_os_type() 1238 if not build_url and self._parser.options.image: 1239 build_url, _ = self.stage_build_for_install( 1240 self._parser.options.image, os_type=os_type) 1241 if os_type == OS_TYPE_ANDROID: 1242 self.install_android( 1243 build_url=build_url, build_local_path=build_local_path, 1244 wipe=wipe, flash_all=flash_all) 1245 elif os_type == OS_TYPE_BRILLO: 1246 self.install_brillo( 1247 build_url=build_url, build_local_path=build_local_path) 1248 else: 1249 raise error.InstallError( 1250 'Installation of os type %s is not supported.' % 1251 self.get_os_type()) 1252 return build_url.split('static/')[-1] 1253 1254 1255 def list_files_glob(self, path_glob): 1256 """Get a list of files on the device given glob pattern path. 1257 1258 @param path_glob: The path glob that we want to return the list of 1259 files that match the glob. Relative paths will not work as 1260 expected. Supply an absolute path to get the list of files 1261 you're hoping for. 1262 1263 @returns List of files that match the path_glob. 1264 """ 1265 # This is just in case path_glob has no path separator. 1266 base_path = os.path.dirname(path_glob) or '.' 1267 result = self.run('find %s -path \'%s\' -print' % 1268 (base_path, path_glob)) 1269 if result.exit_status != 0: 1270 return [] 1271 return result.stdout.splitlines() 1272 1273 1274 def install_apk(self, apk): 1275 """Install the specified apk. 1276 1277 This will install the apk and override it if it's already installed and 1278 will also allow for downgraded apks. 1279 1280 @param apk: The path to apk file. 1281 """ 1282 self.adb_run('install -r -d %s' % apk) 1283