[email protected] | 12f36c8 | 2013-03-29 06:21:13 | [diff] [blame] | 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 | |
[email protected] | 552df57 | 2014-02-26 20:31:35 | [diff] [blame] | 13 | import logging |
[email protected] | 12f36c8 | 2013-03-29 06:21:13 | [diff] [blame] | 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 |
[email protected] | 552df57 | 2014-02-26 20:31:35 | [diff] [blame] | 23 | from pylib import device_settings |
[email protected] | ee7bf01 | 2014-03-24 18:04:57 | [diff] [blame] | 24 | from pylib.cmd_helper import GetCmdOutput |
[email protected] | 044d79b | 2014-04-10 19:37:30 | [diff] [blame] | 25 | from pylib.device import device_utils |
[email protected] | 12f36c8 | 2013-03-29 06:21:13 | [diff] [blame] | 26 | |
[email protected] | 11ef9c0 | 2014-05-22 11:13:40 | [diff] [blame^] | 27 | sys.path.append(os.path.join(constants.DIR_SOURCE_ROOT, |
| 28 | 'third_party', 'android_testrunner')) |
| 29 | import errors |
| 30 | |
[email protected] | 7849a33 | 2013-07-12 01:40:09 | [diff] [blame] | 31 | def KillHostHeartbeat(): |
[email protected] | 12f36c8 | 2013-03-29 06:21:13 | [diff] [blame] | 32 | ps = subprocess.Popen(['ps', 'aux'], stdout = subprocess.PIPE) |
| 33 | stdout, _ = ps.communicate() |
| 34 | matches = re.findall('\\n.*host_heartbeat.*', stdout) |
| 35 | for match in matches: |
[email protected] | 7849a33 | 2013-07-12 01:40:09 | [diff] [blame] | 36 | print 'An instance of host heart beart running... will kill' |
[email protected] | 12f36c8 | 2013-03-29 06:21:13 | [diff] [blame] | 37 | pid = re.findall('(\d+)', match)[1] |
| 38 | subprocess.call(['kill', str(pid)]) |
[email protected] | 7849a33 | 2013-07-12 01:40:09 | [diff] [blame] | 39 | |
| 40 | |
| 41 | def LaunchHostHeartbeat(): |
| 42 | # Kill if existing host_heartbeat |
| 43 | KillHostHeartbeat() |
[email protected] | 12f36c8 | 2013-03-29 06:21:13 | [diff] [blame] | 44 | # Launch a new host_heartbeat |
[email protected] | c89aae0 | 2013-04-06 00:25:19 | [diff] [blame] | 45 | print 'Spawning host heartbeat...' |
[email protected] | b3c0d4a | 2013-06-05 23:28:09 | [diff] [blame] | 46 | subprocess.Popen([os.path.join(constants.DIR_SOURCE_ROOT, |
[email protected] | 12f36c8 | 2013-03-29 06:21:13 | [diff] [blame] | 47 | 'build/android/host_heartbeat.py')]) |
| 48 | |
| 49 | |
[email protected] | c89aae0 | 2013-04-06 00:25:19 | [diff] [blame] | 50 | def PushAndLaunchAdbReboot(devices, target): |
| 51 | """Pushes and launches the adb_reboot binary on the device. |
| 52 | |
| 53 | Arguments: |
| 54 | devices: The list of serial numbers of the device to which the |
| 55 | adb_reboot binary should be pushed. |
| 56 | target : The build target (example, Debug or Release) which helps in |
| 57 | locating the adb_reboot binary. |
| 58 | """ |
[email protected] | 044d79b | 2014-04-10 19:37:30 | [diff] [blame] | 59 | for device_serial in devices: |
| 60 | print 'Will push and launch adb_reboot on %s' % device_serial |
| 61 | device = device_utils.DeviceUtils(device_serial) |
[email protected] | c89aae0 | 2013-04-06 00:25:19 | [diff] [blame] | 62 | # Kill if adb_reboot is already running. |
[email protected] | 044d79b | 2014-04-10 19:37:30 | [diff] [blame] | 63 | device.old_interface.KillAllBlocking('adb_reboot', 2) |
[email protected] | c89aae0 | 2013-04-06 00:25:19 | [diff] [blame] | 64 | # Push adb_reboot |
| 65 | print ' Pushing adb_reboot ...' |
[email protected] | b3c0d4a | 2013-06-05 23:28:09 | [diff] [blame] | 66 | adb_reboot = os.path.join(constants.DIR_SOURCE_ROOT, |
[email protected] | c89aae0 | 2013-04-06 00:25:19 | [diff] [blame] | 67 | 'out/%s/adb_reboot' % target) |
[email protected] | 044d79b | 2014-04-10 19:37:30 | [diff] [blame] | 68 | device.old_interface.PushIfNeeded(adb_reboot, '/data/local/tmp/') |
[email protected] | c89aae0 | 2013-04-06 00:25:19 | [diff] [blame] | 69 | # Launch adb_reboot |
| 70 | print ' Launching adb_reboot ...' |
[email protected] | 7d2b7cf | 2014-05-14 15:15:19 | [diff] [blame] | 71 | device.old_interface.GetAndroidToolStatusAndOutput( |
| 72 | '/data/local/tmp/adb_reboot') |
[email protected] | c89aae0 | 2013-04-06 00:25:19 | [diff] [blame] | 73 | LaunchHostHeartbeat() |
| 74 | |
| 75 | |
[email protected] | 044d79b | 2014-04-10 19:37:30 | [diff] [blame] | 76 | def _ConfigureLocalProperties(device): |
[email protected] | 552df57 | 2014-02-26 20:31:35 | [diff] [blame] | 77 | """Set standard readonly testing device properties prior to reboot.""" |
| 78 | local_props = [ |
| 79 | 'ro.monkey=1', |
| 80 | 'ro.test_harness=1', |
| 81 | 'ro.audio.silent=1', |
| 82 | 'ro.setupwizard.mode=DISABLED', |
| 83 | ] |
[email protected] | 044d79b | 2014-04-10 19:37:30 | [diff] [blame] | 84 | device.old_interface.SetProtectedFileContents( |
| 85 | constants.DEVICE_LOCAL_PROPERTIES_PATH, |
| 86 | '\n'.join(local_props)) |
[email protected] | 552df57 | 2014-02-26 20:31:35 | [diff] [blame] | 87 | # Android will not respect the local props file if it is world writable. |
[email protected] | 044d79b | 2014-04-10 19:37:30 | [diff] [blame] | 88 | device.old_interface.RunShellCommandWithSU( |
| 89 | 'chmod 644 %s' % constants.DEVICE_LOCAL_PROPERTIES_PATH) |
| 90 | |
| 91 | # LOCAL_PROPERTIES_PATH = '/data/local.prop' |
[email protected] | 552df57 | 2014-02-26 20:31:35 | [diff] [blame] | 92 | |
| 93 | |
[email protected] | ee7bf01 | 2014-03-24 18:04:57 | [diff] [blame] | 94 | def WipeDeviceData(device): |
| 95 | """Wipes data from device, keeping only the adb_keys for authorization. |
| 96 | |
| 97 | After wiping data on a device that has been authorized, adb can still |
| 98 | communicate with the device, but after reboot the device will need to be |
| 99 | re-authorized because the adb keys file is stored in /data/misc/adb/. |
[email protected] | 2eeae0b0 | 2014-05-09 21:27:16 | [diff] [blame] | 100 | Thus, adb_keys file is rewritten so the device does not need to be |
| 101 | re-authorized. |
[email protected] | ee7bf01 | 2014-03-24 18:04:57 | [diff] [blame] | 102 | |
| 103 | Arguments: |
| 104 | device: the device to wipe |
| 105 | """ |
[email protected] | 044d79b | 2014-04-10 19:37:30 | [diff] [blame] | 106 | device_authorized = device.old_interface.FileExistsOnDevice( |
| 107 | constants.ADB_KEYS_FILE) |
[email protected] | ee7bf01 | 2014-03-24 18:04:57 | [diff] [blame] | 108 | if device_authorized: |
[email protected] | 044d79b | 2014-04-10 19:37:30 | [diff] [blame] | 109 | adb_keys = device.old_interface.RunShellCommandWithSU( |
[email protected] | 2eeae0b0 | 2014-05-09 21:27:16 | [diff] [blame] | 110 | 'cat %s' % constants.ADB_KEYS_FILE) |
[email protected] | 044d79b | 2014-04-10 19:37:30 | [diff] [blame] | 111 | device.old_interface.RunShellCommandWithSU('wipe data') |
[email protected] | ee7bf01 | 2014-03-24 18:04:57 | [diff] [blame] | 112 | if device_authorized: |
| 113 | path_list = constants.ADB_KEYS_FILE.split('/') |
| 114 | dir_path = '/'.join(path_list[:len(path_list)-1]) |
[email protected] | 044d79b | 2014-04-10 19:37:30 | [diff] [blame] | 115 | device.old_interface.RunShellCommandWithSU('mkdir -p %s' % dir_path) |
[email protected] | 2eeae0b0 | 2014-05-09 21:27:16 | [diff] [blame] | 116 | device.old_interface.RunShellCommand('echo %s > %s' % |
| 117 | (adb_keys[0], constants.ADB_KEYS_FILE)) |
| 118 | for adb_key in adb_keys[1:]: |
| 119 | device.old_interface.RunShellCommand( |
| 120 | 'echo %s >> %s' % (adb_key, constants.ADB_KEYS_FILE)) |
[email protected] | ee7bf01 | 2014-03-24 18:04:57 | [diff] [blame] | 121 | |
| 122 | |
[email protected] | 12f36c8 | 2013-03-29 06:21:13 | [diff] [blame] | 123 | def ProvisionDevices(options): |
| 124 | if options.device is not None: |
| 125 | devices = [options.device] |
| 126 | else: |
| 127 | devices = android_commands.GetAttachedDevices() |
[email protected] | 044d79b | 2014-04-10 19:37:30 | [diff] [blame] | 128 | for device_serial in devices: |
| 129 | device = device_utils.DeviceUtils(device_serial) |
[email protected] | 1f636d3 | 2014-04-26 02:29:41 | [diff] [blame] | 130 | device.old_interface.EnableAdbRoot() |
[email protected] | ee7bf01 | 2014-03-24 18:04:57 | [diff] [blame] | 131 | install_output = GetCmdOutput( |
| 132 | ['%s/build/android/adb_install_apk.py' % constants.DIR_SOURCE_ROOT, |
| 133 | '--apk', |
| 134 | '%s/build/android/CheckInstallApk-debug.apk' % constants.DIR_SOURCE_ROOT |
| 135 | ]) |
| 136 | failure_string = 'Failure [INSTALL_FAILED_INSUFFICIENT_STORAGE]' |
| 137 | if failure_string in install_output: |
| 138 | WipeDeviceData(device) |
[email protected] | 044d79b | 2014-04-10 19:37:30 | [diff] [blame] | 139 | _ConfigureLocalProperties(device) |
[email protected] | 552df57 | 2014-02-26 20:31:35 | [diff] [blame] | 140 | device_settings.ConfigureContentSettingsDict( |
[email protected] | 044d79b | 2014-04-10 19:37:30 | [diff] [blame] | 141 | device, device_settings.DETERMINISTIC_DEVICE_SETTINGS) |
[email protected] | 552df57 | 2014-02-26 20:31:35 | [diff] [blame] | 142 | # TODO(tonyg): We eventually want network on. However, currently radios |
| 143 | # can cause perfbots to drain faster than they charge. |
| 144 | if 'perf' in os.environ.get('BUILDBOT_BUILDERNAME', '').lower(): |
| 145 | device_settings.ConfigureContentSettingsDict( |
[email protected] | 044d79b | 2014-04-10 19:37:30 | [diff] [blame] | 146 | device, device_settings.NETWORK_DISABLED_SETTINGS) |
| 147 | device.old_interface.RunShellCommandWithSU('date -u %f' % time.time()) |
[email protected] | a019dd87 | 2014-04-28 05:54:08 | [diff] [blame] | 148 | (_, props) = device.old_interface.GetShellCommandStatusAndOutput('getprop') |
| 149 | for prop in props: |
| 150 | print prop |
[email protected] | c89aae0 | 2013-04-06 00:25:19 | [diff] [blame] | 151 | if options.auto_reconnect: |
| 152 | PushAndLaunchAdbReboot(devices, options.target) |
[email protected] | 12f36c8 | 2013-03-29 06:21:13 | [diff] [blame] | 153 | |
| 154 | |
| 155 | def main(argv): |
[email protected] | 552df57 | 2014-02-26 20:31:35 | [diff] [blame] | 156 | logging.basicConfig(level=logging.INFO) |
| 157 | |
[email protected] | 12f36c8 | 2013-03-29 06:21:13 | [diff] [blame] | 158 | parser = optparse.OptionParser() |
[email protected] | 2eeae0b0 | 2014-05-09 21:27:16 | [diff] [blame] | 159 | parser.add_option('-w', '--wipe', action='store_true', |
| 160 | help='Wipe device data from all attached devices.') |
[email protected] | 12f36c8 | 2013-03-29 06:21:13 | [diff] [blame] | 161 | parser.add_option('-d', '--device', |
| 162 | help='The serial number of the device to be provisioned') |
[email protected] | c89aae0 | 2013-04-06 00:25:19 | [diff] [blame] | 163 | parser.add_option('-t', '--target', default='Debug', help='The build target') |
| 164 | parser.add_option( |
| 165 | '-r', '--auto-reconnect', action='store_true', |
| 166 | help='Push binary which will reboot the device on adb disconnections.') |
[email protected] | 12f36c8 | 2013-03-29 06:21:13 | [diff] [blame] | 167 | options, args = parser.parse_args(argv[1:]) |
[email protected] | fae5c5b | 2013-09-10 18:29:03 | [diff] [blame] | 168 | constants.SetBuildType(options.target) |
[email protected] | 12f36c8 | 2013-03-29 06:21:13 | [diff] [blame] | 169 | |
| 170 | if args: |
| 171 | print >> sys.stderr, 'Unused args %s' % args |
| 172 | return 1 |
| 173 | |
[email protected] | 2eeae0b0 | 2014-05-09 21:27:16 | [diff] [blame] | 174 | if options.wipe: |
| 175 | devices = android_commands.GetAttachedDevices() |
| 176 | for device_serial in devices: |
| 177 | device = device_utils.DeviceUtils(device_serial) |
| 178 | WipeDeviceData(device) |
[email protected] | 11ef9c0 | 2014-05-22 11:13:40 | [diff] [blame^] | 179 | try: |
| 180 | (device_utils.DeviceUtils.parallel(devices) |
| 181 | .old_interface.Reboot(True).pFinish(None)) |
| 182 | except errors.DeviceUnresponsiveError: |
| 183 | pass |
[email protected] | 2eeae0b0 | 2014-05-09 21:27:16 | [diff] [blame] | 184 | else: |
| 185 | ProvisionDevices(options) |
[email protected] | 12f36c8 | 2013-03-29 06:21:13 | [diff] [blame] | 186 | |
| 187 | |
| 188 | if __name__ == '__main__': |
| 189 | sys.exit(main(sys.argv)) |