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