Home | History | Annotate | Download | only in android
      1 #!/usr/bin/env python
      2 #
      3 # Copyright (c) 2013 The Chromium Authors. All rights reserved.
      4 # Use of this source code is governed by a BSD-style license that can be
      5 # found in the LICENSE file.
      6 
      7 """Provisions Android devices with settings required for bots.
      8 
      9 Usage:
     10   ./provision_devices.py [-d <device serial number>]
     11 """
     12 
     13 import logging
     14 import optparse
     15 import os
     16 import re
     17 import subprocess
     18 import sys
     19 import time
     20 
     21 from pylib import android_commands
     22 from pylib import constants
     23 from pylib import device_settings
     24 from pylib.device import device_blacklist
     25 from pylib.device import device_errors
     26 from pylib.device import device_utils
     27 from pylib.utils import run_tests_helper
     28 
     29 sys.path.append(os.path.join(constants.DIR_SOURCE_ROOT,
     30                              'third_party', 'android_testrunner'))
     31 import errors
     32 
     33 def KillHostHeartbeat():
     34   ps = subprocess.Popen(['ps', 'aux'], stdout = subprocess.PIPE)
     35   stdout, _ = ps.communicate()
     36   matches = re.findall('\\n.*host_heartbeat.*', stdout)
     37   for match in matches:
     38     logging.info('An instance of host heart beart running... will kill')
     39     pid = re.findall('(\S+)', match)[1]
     40     subprocess.call(['kill', str(pid)])
     41 
     42 
     43 def LaunchHostHeartbeat():
     44   # Kill if existing host_heartbeat
     45   KillHostHeartbeat()
     46   # Launch a new host_heartbeat
     47   logging.info('Spawning host heartbeat...')
     48   subprocess.Popen([os.path.join(constants.DIR_SOURCE_ROOT,
     49                                  'build/android/host_heartbeat.py')])
     50 
     51 
     52 def PushAndLaunchAdbReboot(device, target):
     53   """Pushes and launches the adb_reboot binary on the device.
     54 
     55   Arguments:
     56     device: The DeviceUtils instance for the device to which the adb_reboot
     57             binary should be pushed.
     58     target: The build target (example, Debug or Release) which helps in
     59             locating the adb_reboot binary.
     60   """
     61   logging.info('Will push and launch adb_reboot on %s' % str(device))
     62   # Kill if adb_reboot is already running.
     63   try:
     64     # Don't try to kill adb_reboot more than once. We don't expect it to be
     65     # running at all.
     66     device.KillAll('adb_reboot', blocking=True, timeout=2, retries=0)
     67   except device_errors.CommandFailedError:
     68     # We can safely ignore the exception because we don't expect adb_reboot
     69     # to be running.
     70     pass
     71   # Push adb_reboot
     72   logging.info('  Pushing adb_reboot ...')
     73   adb_reboot = os.path.join(constants.DIR_SOURCE_ROOT,
     74                             'out/%s/adb_reboot' % target)
     75   device.PushChangedFiles(adb_reboot, '/data/local/tmp/')
     76   # Launch adb_reboot
     77   logging.info('  Launching adb_reboot ...')
     78   device.old_interface.GetAndroidToolStatusAndOutput(
     79       '/data/local/tmp/adb_reboot')
     80 
     81 
     82 def _ConfigureLocalProperties(device, is_perf):
     83   """Set standard readonly testing device properties prior to reboot."""
     84   local_props = [
     85       'persist.sys.usb.config=adb',
     86       'ro.monkey=1',
     87       'ro.test_harness=1',
     88       'ro.audio.silent=1',
     89       'ro.setupwizard.mode=DISABLED',
     90       ]
     91   if not is_perf:
     92     local_props.append('%s=all' % android_commands.JAVA_ASSERT_PROPERTY)
     93     local_props.append('debug.checkjni=1')
     94   try:
     95     device.WriteFile(
     96         constants.DEVICE_LOCAL_PROPERTIES_PATH,
     97         '\n'.join(local_props), as_root=True)
     98     # Android will not respect the local props file if it is world writable.
     99     device.RunShellCommand(
    100         'chmod 644 %s' % constants.DEVICE_LOCAL_PROPERTIES_PATH,
    101         as_root=True)
    102   except device_errors.CommandFailedError as e:
    103     logging.warning(str(e))
    104 
    105   # LOCAL_PROPERTIES_PATH = '/data/local.prop'
    106 
    107 
    108 def WipeDeviceData(device):
    109   """Wipes data from device, keeping only the adb_keys for authorization.
    110 
    111   After wiping data on a device that has been authorized, adb can still
    112   communicate with the device, but after reboot the device will need to be
    113   re-authorized because the adb keys file is stored in /data/misc/adb/.
    114   Thus, adb_keys file is rewritten so the device does not need to be
    115   re-authorized.
    116 
    117   Arguments:
    118     device: the device to wipe
    119   """
    120   device_authorized = device.FileExists(constants.ADB_KEYS_FILE)
    121   if device_authorized:
    122     adb_keys = device.RunShellCommand('cat %s' % constants.ADB_KEYS_FILE,
    123                                       as_root=True)
    124   device.RunShellCommand('wipe data', as_root=True)
    125   if device_authorized:
    126     path_list = constants.ADB_KEYS_FILE.split('/')
    127     dir_path = '/'.join(path_list[:len(path_list)-1])
    128     device.RunShellCommand('mkdir -p %s' % dir_path, as_root=True)
    129     device.RunShellCommand('restorecon %s' % dir_path, as_root=True)
    130     device.RunShellCommand('echo %s > %s' %
    131                            (adb_keys[0], constants.ADB_KEYS_FILE), as_root=True)
    132     for adb_key in adb_keys[1:]:
    133       device.RunShellCommand(
    134         'echo %s >> %s' % (adb_key, constants.ADB_KEYS_FILE), as_root=True)
    135     device.RunShellCommand('restorecon %s' % constants.ADB_KEYS_FILE,
    136                            as_root=True)
    137 
    138 
    139 def WipeDeviceIfPossible(device):
    140   try:
    141     device.EnableRoot()
    142     WipeDeviceData(device)
    143     # TODO(jbudorick): Tune the timeout per OS version.
    144     device.Reboot(True, timeout=600, retries=0)
    145   except (errors.DeviceUnresponsiveError, device_errors.CommandFailedError):
    146     pass
    147 
    148 
    149 def ProvisionDevice(device, options, is_perf):
    150   try:
    151     if not options.skip_wipe:
    152       WipeDeviceIfPossible(device)
    153     try:
    154       device.EnableRoot()
    155     except device_errors.CommandFailedError as e:
    156       logging.warning(str(e))
    157     _ConfigureLocalProperties(device, is_perf)
    158     device_settings.ConfigureContentSettings(
    159         device, device_settings.DETERMINISTIC_DEVICE_SETTINGS)
    160     if options.disable_location:
    161       device_settings.ConfigureContentSettings(
    162           device, device_settings.DISABLE_LOCATION_SETTINGS)
    163     else:
    164       device_settings.ConfigureContentSettings(
    165           device, device_settings.ENABLE_LOCATION_SETTINGS)
    166     device_settings.SetLockScreenSettings(device)
    167     if is_perf:
    168       # TODO(tonyg): We eventually want network on. However, currently radios
    169       # can cause perfbots to drain faster than they charge.
    170       device_settings.ConfigureContentSettings(
    171           device, device_settings.NETWORK_DISABLED_SETTINGS)
    172       # Some perf bots run benchmarks with USB charging disabled which leads
    173       # to gradual draining of the battery. We must wait for a full charge
    174       # before starting a run in order to keep the devices online.
    175       try:
    176         battery_info = device.old_interface.GetBatteryInfo()
    177       except Exception as e:
    178         battery_info = {}
    179         logging.error('Unable to obtain battery info for %s, %s',
    180                       str(device), e)
    181 
    182       while int(battery_info.get('level', 100)) < 95:
    183         if not device.old_interface.IsDeviceCharging():
    184           if device.old_interface.CanControlUsbCharging():
    185             device.old_interface.EnableUsbCharging()
    186           else:
    187             logging.error('Device is not charging')
    188             break
    189         logging.info('Waiting for device to charge. Current level=%s',
    190                      battery_info.get('level', 0))
    191         time.sleep(60)
    192         battery_info = device.old_interface.GetBatteryInfo()
    193     device.RunShellCommand('date -u %f' % time.time(), as_root=True)
    194     # TODO(jbudorick): Tune the timeout per OS version.
    195     device.Reboot(True, timeout=600, retries=0)
    196     props = device.RunShellCommand('getprop')
    197     for prop in props:
    198       logging.info('  %s' % prop)
    199     if options.auto_reconnect:
    200       PushAndLaunchAdbReboot(device, options.target)
    201   except (errors.WaitForResponseTimedOutError,
    202           device_errors.CommandTimeoutError):
    203     logging.info('Timed out waiting for device %s. Adding to blacklist.',
    204                  str(device))
    205     # Device black list is reset by bb_device_status_check.py per build.
    206     device_blacklist.ExtendBlacklist([str(device)])
    207   except (device_errors.CommandFailedError):
    208     logging.info('Failed to provision device %s. Adding to blacklist.',
    209                  str(device))
    210     device_blacklist.ExtendBlacklist([str(device)])
    211 
    212 
    213 def ProvisionDevices(options):
    214   is_perf = 'perf' in os.environ.get('BUILDBOT_BUILDERNAME', '').lower()
    215   if options.device is not None:
    216     devices = [options.device]
    217   else:
    218     devices = android_commands.GetAttachedDevices()
    219 
    220   parallel_devices = device_utils.DeviceUtils.parallel(devices)
    221   parallel_devices.pMap(ProvisionDevice, options, is_perf)
    222   if options.auto_reconnect:
    223     LaunchHostHeartbeat()
    224   blacklist = device_blacklist.ReadBlacklist()
    225   if all(d in blacklist for d in devices):
    226     raise device_errors.NoDevicesError
    227   return 0
    228 
    229 
    230 def main(argv):
    231   custom_handler = logging.StreamHandler(sys.stdout)
    232   custom_handler.setFormatter(run_tests_helper.CustomFormatter())
    233   logging.getLogger().addHandler(custom_handler)
    234   logging.getLogger().setLevel(logging.INFO)
    235 
    236   parser = optparse.OptionParser()
    237   parser.add_option('--skip-wipe', action='store_true', default=False,
    238                     help="Don't wipe device data during provisioning.")
    239   parser.add_option('--disable-location', action='store_true', default=False,
    240                     help="Disallow Google location services on devices.")
    241   parser.add_option('-d', '--device',
    242                     help='The serial number of the device to be provisioned')
    243   parser.add_option('-t', '--target', default='Debug', help='The build target')
    244   parser.add_option(
    245       '-r', '--auto-reconnect', action='store_true',
    246       help='Push binary which will reboot the device on adb disconnections.')
    247   options, args = parser.parse_args(argv[1:])
    248   constants.SetBuildType(options.target)
    249 
    250   if args:
    251     print >> sys.stderr, 'Unused args %s' % args
    252     return 1
    253 
    254   return ProvisionDevices(options)
    255 
    256 
    257 if __name__ == '__main__':
    258   sys.exit(main(sys.argv))
    259