blob: e4517b606a7524ad8cf4e0146a0f280e25fa8a08 [file] [log] [blame]
[email protected]12f36c82013-03-29 06:21:131#!/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
9Usage:
10 ./provision_devices.py [-d <device serial number>]
11"""
12
perezjub205693d2015-01-23 11:55:4213import argparse
[email protected]552df572014-02-26 20:31:3514import logging
[email protected]12f36c82013-03-29 06:21:1315import os
navabi00c5fd32015-02-26 22:06:4716import posixpath
[email protected]12f36c82013-03-29 06:21:1317import re
18import subprocess
19import sys
20import time
21
[email protected]12f36c82013-03-29 06:21:1322from pylib import constants
[email protected]552df572014-02-26 20:31:3523from pylib import device_settings
jbudorickbfffb22e2015-04-16 14:10:0224from pylib.device import adb_wrapper
rnephew58368d3e2015-04-06 20:02:3825from pylib.device import battery_utils
[email protected]021265ed2014-08-07 00:09:0726from pylib.device import device_blacklist
[email protected]402e54cd2014-06-25 05:09:0627from pylib.device import device_errors
jbudorickbfffb22e2015-04-16 14:10:0228from pylib.device import device_filter
[email protected]044d79b2014-04-10 19:37:3029from pylib.device import device_utils
[email protected]e30597d42014-08-16 08:21:3430from pylib.utils import run_tests_helper
jbudorickd3f50b02015-02-24 18:52:0331from pylib.utils import timeout_retry
[email protected]12f36c82013-03-29 06:21:1332
[email protected]11ef9c02014-05-22 11:13:4033sys.path.append(os.path.join(constants.DIR_SOURCE_ROOT,
34 'third_party', 'android_testrunner'))
35import errors
36
jbudorickb22b9622015-01-29 00:26:2137
38class _DEFAULT_TIMEOUTS(object):
39 # L can take a while to reboot after a wipe.
40 LOLLIPOP = 600
41 PRE_LOLLIPOP = 180
42
43 HELP_TEXT = '{}s on L, {}s on pre-L'.format(LOLLIPOP, PRE_LOLLIPOP)
44
45
jbudorick7f46f7bf2015-04-08 22:57:3946class _PHASES(object):
47 WIPE = 'wipe'
48 PROPERTIES = 'properties'
49 FINISH = 'finish'
50
51 ALL = [WIPE, PROPERTIES, FINISH]
[email protected]7849a332013-07-12 01:40:0952
53
jbudorick7f46f7bf2015-04-08 22:57:3954def ProvisionDevices(options):
55 if options.device is not None:
56 devices = [options.device]
57 else:
jbudorickbfffb22e2015-04-16 14:10:0258 devices = adb_wrapper.AdbWrapper.Devices(
59 filters=device_filter.DefaultFilters())
jbudorick7f46f7bf2015-04-08 22:57:3960
61 parallel_devices = device_utils.DeviceUtils.parallel(devices)
62 parallel_devices.pMap(ProvisionDevice, options)
63 if options.auto_reconnect:
64 _LaunchHostHeartbeat()
65 blacklist = device_blacklist.ReadBlacklist()
66 if all(d in blacklist for d in devices):
67 raise device_errors.NoDevicesError
68 return 0
[email protected]12f36c82013-03-29 06:21:1369
70
jbudorick7f46f7bf2015-04-08 22:57:3971def ProvisionDevice(device, options):
72 if options.reboot_timeout:
73 reboot_timeout = options.reboot_timeout
74 elif (device.build_version_sdk >=
75 constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP):
76 reboot_timeout = _DEFAULT_TIMEOUTS.LOLLIPOP
77 else:
78 reboot_timeout = _DEFAULT_TIMEOUTS.PRE_LOLLIPOP
79
80 def should_run_phase(phase_name):
81 return not options.phases or phase_name in options.phases
82
83 def run_phase(phase_func, reboot=True):
84 device.WaitUntilFullyBooted(timeout=reboot_timeout)
85 phase_func(device, options)
86 if reboot:
87 device.Reboot(False, retries=0)
88 device.adb.WaitForDevice()
89
jbudorick9e8fa5da2015-04-09 23:40:2590 try:
91 if should_run_phase(_PHASES.WIPE):
92 run_phase(WipeDevice)
jbudorick7f46f7bf2015-04-08 22:57:3993
jbudorick9e8fa5da2015-04-09 23:40:2594 if should_run_phase(_PHASES.PROPERTIES):
95 run_phase(SetProperties)
jbudorick7f46f7bf2015-04-08 22:57:3996
jbudorick9e8fa5da2015-04-09 23:40:2597 if should_run_phase(_PHASES.FINISH):
98 run_phase(FinishProvisioning, reboot=False)
99
100 except (errors.WaitForResponseTimedOutError,
101 device_errors.CommandTimeoutError):
102 logging.exception('Timed out waiting for device %s. Adding to blacklist.',
103 str(device))
104 device_blacklist.ExtendBlacklist([str(device)])
105
106 except device_errors.CommandFailedError:
107 logging.exception('Failed to provision device %s. Adding to blacklist.',
108 str(device))
109 device_blacklist.ExtendBlacklist([str(device)])
jbudorick7f46f7bf2015-04-08 22:57:39110
111
112def WipeDevice(device, options):
113 """Wipes data from device, keeping only the adb_keys for authorization.
114
115 After wiping data on a device that has been authorized, adb can still
116 communicate with the device, but after reboot the device will need to be
117 re-authorized because the adb keys file is stored in /data/misc/adb/.
118 Thus, adb_keys file is rewritten so the device does not need to be
119 re-authorized.
120
121 Arguments:
122 device: the device to wipe
123 """
124 if options.skip_wipe:
125 return
126
jbudorick7f46f7bf2015-04-08 22:57:39127 try:
jbudorick9e8fa5da2015-04-09 23:40:25128 device.EnableRoot()
129 device_authorized = device.FileExists(constants.ADB_KEYS_FILE)
130 if device_authorized:
131 adb_keys = device.ReadFile(constants.ADB_KEYS_FILE,
132 as_root=True).splitlines()
jbudorick7f46f7bf2015-04-08 22:57:39133 device.RunShellCommand(['wipe', 'data'],
jbudorick809d9aff2015-04-09 16:45:29134 as_root=True, check_return=True)
jbudorick9e8fa5da2015-04-09 23:40:25135 device.adb.WaitForDevice()
136
137 if device_authorized:
138 adb_keys_set = set(adb_keys)
139 for adb_key_file in options.adb_key_files or []:
140 try:
141 with open(adb_key_file, 'r') as f:
142 adb_public_keys = f.readlines()
143 adb_keys_set.update(adb_public_keys)
144 except IOError:
145 logging.warning('Unable to find adb keys file %s.' % adb_key_file)
146 _WriteAdbKeysFile(device, '\n'.join(adb_keys_set))
jbudorick7f46f7bf2015-04-08 22:57:39147 except device_errors.CommandFailedError:
148 logging.exception('Possible failure while wiping the device. '
149 'Attempting to continue.')
jbudorick7f46f7bf2015-04-08 22:57:39150
151
152def _WriteAdbKeysFile(device, adb_keys_string):
153 dir_path = posixpath.dirname(constants.ADB_KEYS_FILE)
154 device.RunShellCommand(['mkdir', '-p', dir_path],
155 as_root=True, check_return=True)
156 device.RunShellCommand(['restorecon', dir_path],
157 as_root=True, check_return=True)
158 device.WriteFile(constants.ADB_KEYS_FILE, adb_keys_string, as_root=True)
159 device.RunShellCommand(['restorecon', constants.ADB_KEYS_FILE],
160 as_root=True, check_return=True)
161
162
163def SetProperties(device, options):
164 try:
165 device.EnableRoot()
166 except device_errors.CommandFailedError as e:
167 logging.warning(str(e))
168
169 _ConfigureLocalProperties(device, options.enable_java_debug)
170 device_settings.ConfigureContentSettings(
171 device, device_settings.DETERMINISTIC_DEVICE_SETTINGS)
172 if options.disable_location:
173 device_settings.ConfigureContentSettings(
174 device, device_settings.DISABLE_LOCATION_SETTINGS)
175 else:
176 device_settings.ConfigureContentSettings(
177 device, device_settings.ENABLE_LOCATION_SETTINGS)
178 device_settings.SetLockScreenSettings(device)
179 if options.disable_network:
180 device_settings.ConfigureContentSettings(
181 device, device_settings.NETWORK_DISABLED_SETTINGS)
182
183 if options.min_battery_level is not None:
184 try:
185 battery = battery_utils.BatteryUtils(device)
186 battery.ChargeDeviceToLevel(options.min_battery_level)
187 except device_errors.CommandFailedError as e:
188 logging.exception('Unable to charge device to specified level.')
189
190
191def _ConfigureLocalProperties(device, java_debug=True):
192 """Set standard readonly testing device properties prior to reboot."""
193 local_props = [
194 'persist.sys.usb.config=adb',
195 'ro.monkey=1',
196 'ro.test_harness=1',
197 'ro.audio.silent=1',
198 'ro.setupwizard.mode=DISABLED',
199 ]
200 if java_debug:
jbudorickbfffb22e2015-04-16 14:10:02201 local_props.append(
202 '%s=all' % device_utils.DeviceUtils.JAVA_ASSERT_PROPERTY)
jbudorick7f46f7bf2015-04-08 22:57:39203 local_props.append('debug.checkjni=1')
204 try:
205 device.WriteFile(
206 constants.DEVICE_LOCAL_PROPERTIES_PATH,
207 '\n'.join(local_props), as_root=True)
208 # Android will not respect the local props file if it is world writable.
209 device.RunShellCommand(
210 ['chmod', '644', constants.DEVICE_LOCAL_PROPERTIES_PATH],
211 as_root=True, check_return=True)
jbudorick9e8fa5da2015-04-09 23:40:25212 except device_errors.CommandFailedError:
213 logging.exception('Failed to configure local properties.')
jbudorick7f46f7bf2015-04-08 22:57:39214
215
216def FinishProvisioning(device, options):
jbudorick9e8fa5da2015-04-09 23:40:25217 device.RunShellCommand(
218 ['date', '-s', time.strftime('%Y%m%d.%H%M%S', time.gmtime())],
219 as_root=True, check_return=True)
220 props = device.RunShellCommand('getprop', check_return=True)
221 for prop in props:
222 logging.info(' %s' % prop)
223 if options.auto_reconnect:
224 _PushAndLaunchAdbReboot(device, options.target)
jbudorick7f46f7bf2015-04-08 22:57:39225
226
227def _PushAndLaunchAdbReboot(device, target):
[email protected]c89aae02013-04-06 00:25:19228 """Pushes and launches the adb_reboot binary on the device.
229
230 Arguments:
[email protected]af4457b2014-08-21 04:17:07231 device: The DeviceUtils instance for the device to which the adb_reboot
232 binary should be pushed.
233 target: The build target (example, Debug or Release) which helps in
234 locating the adb_reboot binary.
[email protected]c89aae02013-04-06 00:25:19235 """
[email protected]af4457b2014-08-21 04:17:07236 logging.info('Will push and launch adb_reboot on %s' % str(device))
237 # Kill if adb_reboot is already running.
perezju84436c82015-04-15 10:19:22238 device.KillAll('adb_reboot', blocking=True, timeout=2, quiet=True)
[email protected]af4457b2014-08-21 04:17:07239 # Push adb_reboot
240 logging.info(' Pushing adb_reboot ...')
241 adb_reboot = os.path.join(constants.DIR_SOURCE_ROOT,
242 'out/%s/adb_reboot' % target)
jbudorick8c07d892014-10-13 11:39:48243 device.PushChangedFiles([(adb_reboot, '/data/local/tmp/')])
[email protected]af4457b2014-08-21 04:17:07244 # Launch adb_reboot
245 logging.info(' Launching adb_reboot ...')
jbudorick7f46f7bf2015-04-08 22:57:39246 device.RunShellCommand(
247 [device.GetDevicePieWrapper(), '/data/local/tmp/adb_reboot'],
248 check_return=True)
[email protected]c89aae02013-04-06 00:25:19249
250
jbudorick7f46f7bf2015-04-08 22:57:39251def _LaunchHostHeartbeat():
252 # Kill if existing host_heartbeat
jbudorick809d9aff2015-04-09 16:45:29253 KillHostHeartbeat()
jbudorick7f46f7bf2015-04-08 22:57:39254 # Launch a new host_heartbeat
255 logging.info('Spawning host heartbeat...')
256 subprocess.Popen([os.path.join(constants.DIR_SOURCE_ROOT,
257 'build/android/host_heartbeat.py')])
navabi00c5fd32015-02-26 22:06:47258
[email protected]552df572014-02-26 20:31:35259
jbudorick809d9aff2015-04-09 16:45:29260def KillHostHeartbeat():
jbudorick7f46f7bf2015-04-08 22:57:39261 ps = subprocess.Popen(['ps', 'aux'], stdout=subprocess.PIPE)
262 stdout, _ = ps.communicate()
263 matches = re.findall('\\n.*host_heartbeat.*', stdout)
264 for match in matches:
265 logging.info('An instance of host heart beart running... will kill')
266 pid = re.findall(r'(\S+)', match)[1]
267 subprocess.call(['kill', str(pid)])
[email protected]12f36c82013-03-29 06:21:13268
269
perezjub205693d2015-01-23 11:55:42270def main():
perezjub205693d2015-01-23 11:55:42271 # Recommended options on perf bots:
272 # --disable-network
273 # TODO(tonyg): We eventually want network on. However, currently radios
274 # can cause perfbots to drain faster than they charge.
perezju545a9ce2015-02-19 13:14:20275 # --min-battery-level 95
perezjub205693d2015-01-23 11:55:42276 # Some perf bots run benchmarks with USB charging disabled which leads
277 # to gradual draining of the battery. We must wait for a full charge
278 # before starting a run in order to keep the devices online.
[email protected]12f36c82013-03-29 06:21:13279
perezjub205693d2015-01-23 11:55:42280 parser = argparse.ArgumentParser(
281 description='Provision Android devices with settings required for bots.')
282 parser.add_argument('-d', '--device', metavar='SERIAL',
283 help='the serial number of the device to be provisioned'
284 ' (the default is to provision all devices attached)')
jbudorick7f46f7bf2015-04-08 22:57:39285 parser.add_argument('--phase', action='append', choices=_PHASES.ALL,
286 dest='phases',
287 help='Phases of provisioning to run. '
288 '(If omitted, all phases will be run.)')
perezjub205693d2015-01-23 11:55:42289 parser.add_argument('--skip-wipe', action='store_true', default=False,
290 help="don't wipe device data during provisioning")
jbudorickb22b9622015-01-29 00:26:21291 parser.add_argument('--reboot-timeout', metavar='SECS', type=int,
perezjub205693d2015-01-23 11:55:42292 help='when wiping the device, max number of seconds to'
jbudorickb22b9622015-01-29 00:26:21293 ' wait after each reboot '
294 '(default: %s)' % _DEFAULT_TIMEOUTS.HELP_TEXT)
perezju545a9ce2015-02-19 13:14:20295 parser.add_argument('--min-battery-level', type=int, metavar='NUM',
296 help='wait for the device to reach this minimum battery'
297 ' level before trying to continue')
perezjub205693d2015-01-23 11:55:42298 parser.add_argument('--disable-location', action='store_true',
299 help='disable Google location services on devices')
300 parser.add_argument('--disable-network', action='store_true',
perezjub205693d2015-01-23 11:55:42301 help='disable network access on devices')
302 parser.add_argument('--disable-java-debug', action='store_false',
perezju545a9ce2015-02-19 13:14:20303 dest='enable_java_debug', default=True,
perezjub205693d2015-01-23 11:55:42304 help='disable Java property asserts and JNI checking')
305 parser.add_argument('-t', '--target', default='Debug',
306 help='the build target (default: %(default)s)')
307 parser.add_argument('-r', '--auto-reconnect', action='store_true',
308 help='push binary which will reboot the device on adb'
309 ' disconnections')
navabi00c5fd32015-02-26 22:06:47310 parser.add_argument('--adb-key-files', type=str, nargs='+',
311 help='list of adb keys to push to device')
jbudorick7f46f7bf2015-04-08 22:57:39312 parser.add_argument('-v', '--verbose', action='count', default=1,
313 help='Log more information.')
perezjub205693d2015-01-23 11:55:42314 args = parser.parse_args()
315 constants.SetBuildType(args.target)
316
jbudorick7f46f7bf2015-04-08 22:57:39317 run_tests_helper.SetLogLevel(args.verbose)
318
perezjub205693d2015-01-23 11:55:42319 return ProvisionDevices(args)
[email protected]12f36c82013-03-29 06:21:13320
321
322if __name__ == '__main__':
perezjub205693d2015-01-23 11:55:42323 sys.exit(main())