Home | History | Annotate | Download | only in tools
      1 #!/usr/bin/env python
      2 # Copyright 2016 The Chromium Authors. All rights reserved.
      3 # Use of this source code is governed by a BSD-style license that can be
      4 # found in the LICENSE file.
      5 
      6 """A script to recover devices in a known bad state."""
      7 
      8 import argparse
      9 import logging
     10 import os
     11 import psutil
     12 import signal
     13 import sys
     14 
     15 if __name__ == '__main__':
     16   sys.path.append(
     17       os.path.abspath(os.path.join(os.path.dirname(__file__),
     18                                    '..', '..', '..')))
     19 from devil import devil_env
     20 from devil.android import device_blacklist
     21 from devil.android import device_errors
     22 from devil.android import device_utils
     23 from devil.android.tools import device_status
     24 from devil.utils import lsusb
     25 from devil.utils import reset_usb
     26 from devil.utils import run_tests_helper
     27 
     28 
     29 def KillAllAdb():
     30   def get_all_adb():
     31     for p in psutil.process_iter():
     32       try:
     33         if 'adb' in p.name:
     34           yield p
     35       except (psutil.NoSuchProcess, psutil.AccessDenied):
     36         pass
     37 
     38   for sig in [signal.SIGTERM, signal.SIGQUIT, signal.SIGKILL]:
     39     for p in get_all_adb():
     40       try:
     41         logging.info('kill %d %d (%s [%s])', sig, p.pid, p.name,
     42                      ' '.join(p.cmdline))
     43         p.send_signal(sig)
     44       except (psutil.NoSuchProcess, psutil.AccessDenied):
     45         pass
     46   for p in get_all_adb():
     47     try:
     48       logging.error('Unable to kill %d (%s [%s])', p.pid, p.name,
     49                     ' '.join(p.cmdline))
     50     except (psutil.NoSuchProcess, psutil.AccessDenied):
     51       pass
     52 
     53 
     54 def RecoverDevice(device, blacklist, should_reboot=lambda device: True):
     55   if device_status.IsBlacklisted(device.adb.GetDeviceSerial(),
     56                                  blacklist):
     57     logging.debug('%s is blacklisted, skipping recovery.', str(device))
     58     return
     59 
     60   if should_reboot(device):
     61     try:
     62       device.WaitUntilFullyBooted(retries=0)
     63     except (device_errors.CommandTimeoutError,
     64             device_errors.CommandFailedError):
     65       logging.exception('Failure while waiting for %s. '
     66                         'Attempting to recover.', str(device))
     67     try:
     68       try:
     69         device.Reboot(block=False, timeout=5, retries=0)
     70       except device_errors.CommandTimeoutError:
     71         logging.warning('Timed out while attempting to reboot %s normally.'
     72                         'Attempting alternative reboot.', str(device))
     73         # The device drops offline before we can grab the exit code, so
     74         # we don't check for status.
     75         device.adb.Root()
     76         device.adb.Shell('echo b > /proc/sysrq-trigger', expect_status=None,
     77                          timeout=5, retries=0)
     78     except device_errors.CommandFailedError:
     79       logging.exception('Failed to reboot %s.', str(device))
     80       if blacklist:
     81         blacklist.Extend([device.adb.GetDeviceSerial()],
     82                          reason='reboot_failure')
     83     except device_errors.CommandTimeoutError:
     84       logging.exception('Timed out while rebooting %s.', str(device))
     85       if blacklist:
     86         blacklist.Extend([device.adb.GetDeviceSerial()],
     87                          reason='reboot_timeout')
     88 
     89     try:
     90       device.WaitUntilFullyBooted(retries=0)
     91     except device_errors.CommandFailedError:
     92       logging.exception('Failure while waiting for %s.', str(device))
     93       if blacklist:
     94         blacklist.Extend([device.adb.GetDeviceSerial()],
     95                          reason='reboot_failure')
     96     except device_errors.CommandTimeoutError:
     97       logging.exception('Timed out while waiting for %s.', str(device))
     98       if blacklist:
     99         blacklist.Extend([device.adb.GetDeviceSerial()],
    100                          reason='reboot_timeout')
    101 
    102 
    103 def RecoverDevices(devices, blacklist):
    104   """Attempts to recover any inoperable devices in the provided list.
    105 
    106   Args:
    107     devices: The list of devices to attempt to recover.
    108     blacklist: The current device blacklist, which will be used then
    109       reset.
    110   """
    111 
    112   statuses = device_status.DeviceStatus(devices, blacklist)
    113 
    114   should_restart_usb = set(
    115       status['serial'] for status in statuses
    116       if (not status['usb_status']
    117           or status['adb_status'] in ('offline', 'missing')))
    118   should_restart_adb = should_restart_usb.union(set(
    119       status['serial'] for status in statuses
    120       if status['adb_status'] == 'unauthorized'))
    121   should_reboot_device = should_restart_adb.union(set(
    122       status['serial'] for status in statuses
    123       if status['blacklisted']))
    124 
    125   logging.debug('Should restart USB for:')
    126   for d in should_restart_usb:
    127     logging.debug('  %s', d)
    128   logging.debug('Should restart ADB for:')
    129   for d in should_restart_adb:
    130     logging.debug('  %s', d)
    131   logging.debug('Should reboot:')
    132   for d in should_reboot_device:
    133     logging.debug('  %s', d)
    134 
    135   if blacklist:
    136     blacklist.Reset()
    137 
    138   if should_restart_adb:
    139     KillAllAdb()
    140   for serial in should_restart_usb:
    141     try:
    142       reset_usb.reset_android_usb(serial)
    143     except IOError:
    144       logging.exception('Unable to reset USB for %s.', serial)
    145       if blacklist:
    146         blacklist.Extend([serial], reason='USB failure')
    147     except device_errors.DeviceUnreachableError:
    148       logging.exception('Unable to reset USB for %s.', serial)
    149       if blacklist:
    150         blacklist.Extend([serial], reason='offline')
    151 
    152   device_utils.DeviceUtils.parallel(devices).pMap(
    153       RecoverDevice, blacklist,
    154       should_reboot=lambda device: device in should_reboot_device)
    155 
    156 
    157 def main():
    158   parser = argparse.ArgumentParser()
    159   parser.add_argument('--adb-path',
    160                       help='Absolute path to the adb binary to use.')
    161   parser.add_argument('--blacklist-file', help='Device blacklist JSON file.')
    162   parser.add_argument('--known-devices-file', action='append', default=[],
    163                       dest='known_devices_files',
    164                       help='Path to known device lists.')
    165   parser.add_argument('-v', '--verbose', action='count', default=1,
    166                       help='Log more information.')
    167 
    168   args = parser.parse_args()
    169   run_tests_helper.SetLogLevel(args.verbose)
    170 
    171   devil_dynamic_config = {
    172     'config_type': 'BaseConfig',
    173     'dependencies': {},
    174   }
    175 
    176   if args.adb_path:
    177     devil_dynamic_config['dependencies'].update({
    178         'adb': {
    179           'file_info': {
    180             devil_env.GetPlatform(): {
    181               'local_paths': [args.adb_path]
    182             }
    183           }
    184         }
    185     })
    186   devil_env.config.Initialize(configs=[devil_dynamic_config])
    187 
    188   blacklist = (device_blacklist.Blacklist(args.blacklist_file)
    189                if args.blacklist_file
    190                else None)
    191 
    192   expected_devices = device_status.GetExpectedDevices(args.known_devices_files)
    193   usb_devices = set(lsusb.get_android_devices())
    194   devices = [device_utils.DeviceUtils(s)
    195              for s in expected_devices.union(usb_devices)]
    196 
    197   RecoverDevices(devices, blacklist)
    198 
    199 
    200 if __name__ == '__main__':
    201   sys.exit(main())
    202