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 # TODO(jbudorick): Resolve this after experimenting w/ disabling the USB reset. 26 from devil.utils import reset_usb # pylint: disable=unused-import 27 from devil.utils import run_tests_helper 28 29 logger = logging.getLogger(__name__) 30 31 32 def KillAllAdb(): 33 def get_all_adb(): 34 for p in psutil.process_iter(): 35 try: 36 if 'adb' in p.name: 37 yield p 38 except (psutil.NoSuchProcess, psutil.AccessDenied): 39 pass 40 41 for sig in [signal.SIGTERM, signal.SIGQUIT, signal.SIGKILL]: 42 for p in get_all_adb(): 43 try: 44 logger.info('kill %d %d (%s [%s])', sig, p.pid, p.name, 45 ' '.join(p.cmdline)) 46 p.send_signal(sig) 47 except (psutil.NoSuchProcess, psutil.AccessDenied): 48 pass 49 for p in get_all_adb(): 50 try: 51 logger.error('Unable to kill %d (%s [%s])', p.pid, p.name, 52 ' '.join(p.cmdline)) 53 except (psutil.NoSuchProcess, psutil.AccessDenied): 54 pass 55 56 57 def RecoverDevice(device, blacklist, should_reboot=lambda device: True): 58 if device_status.IsBlacklisted(device.adb.GetDeviceSerial(), 59 blacklist): 60 logger.debug('%s is blacklisted, skipping recovery.', str(device)) 61 return 62 63 if should_reboot(device): 64 try: 65 device.WaitUntilFullyBooted(retries=0) 66 except (device_errors.CommandTimeoutError, 67 device_errors.CommandFailedError, 68 device_errors.DeviceUnreachableError): 69 logger.exception('Failure while waiting for %s. ' 70 'Attempting to recover.', str(device)) 71 try: 72 try: 73 device.Reboot(block=False, timeout=5, retries=0) 74 except device_errors.CommandTimeoutError: 75 logger.warning('Timed out while attempting to reboot %s normally.' 76 'Attempting alternative reboot.', str(device)) 77 # The device drops offline before we can grab the exit code, so 78 # we don't check for status. 79 try: 80 device.adb.Root() 81 finally: 82 # We are already in a failure mode, attempt to reboot regardless of 83 # what device.adb.Root() returns. If the sysrq reboot fails an 84 # exception willbe thrown at that level. 85 device.adb.Shell('echo b > /proc/sysrq-trigger', expect_status=None, 86 timeout=5, retries=0) 87 except (device_errors.CommandFailedError, 88 device_errors.DeviceUnreachableError): 89 logger.exception('Failed to reboot %s.', str(device)) 90 if blacklist: 91 blacklist.Extend([device.adb.GetDeviceSerial()], 92 reason='reboot_failure') 93 except device_errors.CommandTimeoutError: 94 logger.exception('Timed out while rebooting %s.', str(device)) 95 if blacklist: 96 blacklist.Extend([device.adb.GetDeviceSerial()], 97 reason='reboot_timeout') 98 99 try: 100 device.WaitUntilFullyBooted( 101 retries=0, timeout=device.REBOOT_DEFAULT_TIMEOUT) 102 except (device_errors.CommandFailedError, 103 device_errors.DeviceUnreachableError): 104 logger.exception('Failure while waiting for %s.', str(device)) 105 if blacklist: 106 blacklist.Extend([device.adb.GetDeviceSerial()], 107 reason='reboot_failure') 108 except device_errors.CommandTimeoutError: 109 logger.exception('Timed out while waiting for %s.', str(device)) 110 if blacklist: 111 blacklist.Extend([device.adb.GetDeviceSerial()], 112 reason='reboot_timeout') 113 114 115 def RecoverDevices(devices, blacklist, enable_usb_reset=False): 116 """Attempts to recover any inoperable devices in the provided list. 117 118 Args: 119 devices: The list of devices to attempt to recover. 120 blacklist: The current device blacklist, which will be used then 121 reset. 122 """ 123 124 statuses = device_status.DeviceStatus(devices, blacklist) 125 126 should_restart_usb = set( 127 status['serial'] for status in statuses 128 if (not status['usb_status'] 129 or status['adb_status'] in ('offline', 'missing'))) 130 should_restart_adb = should_restart_usb.union(set( 131 status['serial'] for status in statuses 132 if status['adb_status'] == 'unauthorized')) 133 should_reboot_device = should_restart_adb.union(set( 134 status['serial'] for status in statuses 135 if status['blacklisted'])) 136 137 logger.debug('Should restart USB for:') 138 for d in should_restart_usb: 139 logger.debug(' %s', d) 140 logger.debug('Should restart ADB for:') 141 for d in should_restart_adb: 142 logger.debug(' %s', d) 143 logger.debug('Should reboot:') 144 for d in should_reboot_device: 145 logger.debug(' %s', d) 146 147 if blacklist: 148 blacklist.Reset() 149 150 if should_restart_adb: 151 KillAllAdb() 152 for serial in should_restart_usb: 153 try: 154 # TODO(crbug.com/642194): Resetting may be causing more harm 155 # (specifically, kernel panics) than it does good. 156 if enable_usb_reset: 157 reset_usb.reset_android_usb(serial) 158 else: 159 logger.warning('USB reset disabled for %s (crbug.com/642914)', 160 serial) 161 except IOError: 162 logger.exception('Unable to reset USB for %s.', serial) 163 if blacklist: 164 blacklist.Extend([serial], reason='USB failure') 165 except device_errors.DeviceUnreachableError: 166 logger.exception('Unable to reset USB for %s.', serial) 167 if blacklist: 168 blacklist.Extend([serial], reason='offline') 169 170 device_utils.DeviceUtils.parallel(devices).pMap( 171 RecoverDevice, blacklist, 172 should_reboot=lambda device: device.serial in should_reboot_device) 173 174 175 def main(): 176 parser = argparse.ArgumentParser() 177 parser.add_argument('--adb-path', 178 help='Absolute path to the adb binary to use.') 179 parser.add_argument('--blacklist-file', help='Device blacklist JSON file.') 180 parser.add_argument('--known-devices-file', action='append', default=[], 181 dest='known_devices_files', 182 help='Path to known device lists.') 183 parser.add_argument('--enable-usb-reset', action='store_true', 184 help='Reset USB if necessary.') 185 parser.add_argument('-v', '--verbose', action='count', default=1, 186 help='Log more information.') 187 188 args = parser.parse_args() 189 run_tests_helper.SetLogLevel(args.verbose) 190 191 devil_dynamic_config = devil_env.EmptyConfig() 192 if args.adb_path: 193 devil_dynamic_config['dependencies'].update( 194 devil_env.LocalConfigItem( 195 'adb', devil_env.GetPlatform(), args.adb_path)) 196 devil_env.config.Initialize(configs=[devil_dynamic_config]) 197 198 blacklist = (device_blacklist.Blacklist(args.blacklist_file) 199 if args.blacklist_file 200 else None) 201 202 expected_devices = device_status.GetExpectedDevices(args.known_devices_files) 203 usb_devices = set(lsusb.get_android_devices()) 204 devices = [device_utils.DeviceUtils(s) 205 for s in expected_devices.union(usb_devices)] 206 207 RecoverDevices(devices, blacklist, enable_usb_reset=args.enable_usb_reset) 208 209 210 if __name__ == '__main__': 211 sys.exit(main()) 212