Move UpdateManifest test from unit_tests into extensions_unittests.
[chromium-blink-merge.git] / build / android / provision_devices.py
blobb69a83256325db5795fcbf2fdea496e1f3e2f353
1 #!/usr/bin/env python
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.
7 """Provisions Android devices with settings required for bots.
9 Usage:
10 ./provision_devices.py [-d <device serial number>]
11 """
13 import argparse
14 import json
15 import logging
16 import os
17 import posixpath
18 import re
19 import subprocess
20 import sys
21 import time
23 from pylib import constants
24 from pylib import device_settings
25 from pylib.device import battery_utils
26 from pylib.device import device_blacklist
27 from pylib.device import device_errors
28 from pylib.device import device_utils
29 from pylib.utils import run_tests_helper
30 from pylib.utils import timeout_retry
32 sys.path.append(os.path.join(constants.DIR_SOURCE_ROOT,
33 'third_party', 'android_testrunner'))
34 import errors
37 class _DEFAULT_TIMEOUTS(object):
38 # L can take a while to reboot after a wipe.
39 LOLLIPOP = 600
40 PRE_LOLLIPOP = 180
42 HELP_TEXT = '{}s on L, {}s on pre-L'.format(LOLLIPOP, PRE_LOLLIPOP)
45 class _PHASES(object):
46 WIPE = 'wipe'
47 PROPERTIES = 'properties'
48 FINISH = 'finish'
50 ALL = [WIPE, PROPERTIES, FINISH]
53 def ProvisionDevices(options):
54 devices = device_utils.DeviceUtils.HealthyDevices()
55 if options.device:
56 devices = [d for d in devices if d == options.device]
57 if not devices:
58 raise device_errors.DeviceUnreachableError(options.device)
60 parallel_devices = device_utils.DeviceUtils.parallel(devices)
61 parallel_devices.pMap(ProvisionDevice, options)
62 if options.auto_reconnect:
63 _LaunchHostHeartbeat()
64 blacklist = device_blacklist.ReadBlacklist()
65 if options.output_device_blacklist:
66 with open(options.output_device_blacklist, 'w') as f:
67 json.dump(blacklist, f)
68 if all(d in blacklist for d in devices):
69 raise device_errors.NoDevicesError
70 return 0
73 def ProvisionDevice(device, options):
74 if options.reboot_timeout:
75 reboot_timeout = options.reboot_timeout
76 elif (device.build_version_sdk >=
77 constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP):
78 reboot_timeout = _DEFAULT_TIMEOUTS.LOLLIPOP
79 else:
80 reboot_timeout = _DEFAULT_TIMEOUTS.PRE_LOLLIPOP
82 def should_run_phase(phase_name):
83 return not options.phases or phase_name in options.phases
85 def run_phase(phase_func, reboot=True):
86 device.WaitUntilFullyBooted(timeout=reboot_timeout)
87 phase_func(device, options)
88 if reboot:
89 device.Reboot(False, retries=0)
90 device.adb.WaitForDevice()
92 try:
93 if should_run_phase(_PHASES.WIPE):
94 run_phase(WipeDevice)
96 if should_run_phase(_PHASES.PROPERTIES):
97 run_phase(SetProperties)
99 if should_run_phase(_PHASES.FINISH):
100 run_phase(FinishProvisioning, reboot=False)
102 except (errors.WaitForResponseTimedOutError,
103 device_errors.CommandTimeoutError):
104 logging.exception('Timed out waiting for device %s. Adding to blacklist.',
105 str(device))
106 device_blacklist.ExtendBlacklist([str(device)])
108 except device_errors.CommandFailedError:
109 logging.exception('Failed to provision device %s. Adding to blacklist.',
110 str(device))
111 device_blacklist.ExtendBlacklist([str(device)])
114 def WipeDevice(device, options):
115 """Wipes data from device, keeping only the adb_keys for authorization.
117 After wiping data on a device that has been authorized, adb can still
118 communicate with the device, but after reboot the device will need to be
119 re-authorized because the adb keys file is stored in /data/misc/adb/.
120 Thus, adb_keys file is rewritten so the device does not need to be
121 re-authorized.
123 Arguments:
124 device: the device to wipe
126 if options.skip_wipe:
127 return
129 try:
130 device.EnableRoot()
131 device_authorized = device.FileExists(constants.ADB_KEYS_FILE)
132 if device_authorized:
133 adb_keys = device.ReadFile(constants.ADB_KEYS_FILE,
134 as_root=True).splitlines()
135 device.RunShellCommand(['wipe', 'data'],
136 as_root=True, check_return=True)
137 device.adb.WaitForDevice()
139 if device_authorized:
140 adb_keys_set = set(adb_keys)
141 for adb_key_file in options.adb_key_files or []:
142 try:
143 with open(adb_key_file, 'r') as f:
144 adb_public_keys = f.readlines()
145 adb_keys_set.update(adb_public_keys)
146 except IOError:
147 logging.warning('Unable to find adb keys file %s.' % adb_key_file)
148 _WriteAdbKeysFile(device, '\n'.join(adb_keys_set))
149 except device_errors.CommandFailedError:
150 logging.exception('Possible failure while wiping the device. '
151 'Attempting to continue.')
154 def _WriteAdbKeysFile(device, adb_keys_string):
155 dir_path = posixpath.dirname(constants.ADB_KEYS_FILE)
156 device.RunShellCommand(['mkdir', '-p', dir_path],
157 as_root=True, check_return=True)
158 device.RunShellCommand(['restorecon', dir_path],
159 as_root=True, check_return=True)
160 device.WriteFile(constants.ADB_KEYS_FILE, adb_keys_string, as_root=True)
161 device.RunShellCommand(['restorecon', constants.ADB_KEYS_FILE],
162 as_root=True, check_return=True)
165 def SetProperties(device, options):
166 try:
167 device.EnableRoot()
168 except device_errors.CommandFailedError as e:
169 logging.warning(str(e))
171 _ConfigureLocalProperties(device, options.enable_java_debug)
172 device_settings.ConfigureContentSettings(
173 device, device_settings.DETERMINISTIC_DEVICE_SETTINGS)
174 if options.disable_location:
175 device_settings.ConfigureContentSettings(
176 device, device_settings.DISABLE_LOCATION_SETTINGS)
177 else:
178 device_settings.ConfigureContentSettings(
179 device, device_settings.ENABLE_LOCATION_SETTINGS)
181 if options.disable_mock_location:
182 device_settings.ConfigureContentSettings(
183 device, device_settings.DISABLE_MOCK_LOCATION_SETTINGS)
184 else:
185 device_settings.ConfigureContentSettings(
186 device, device_settings.ENABLE_MOCK_LOCATION_SETTINGS)
188 device_settings.SetLockScreenSettings(device)
189 if options.disable_network:
190 device_settings.ConfigureContentSettings(
191 device, device_settings.NETWORK_DISABLED_SETTINGS)
193 def _ConfigureLocalProperties(device, java_debug=True):
194 """Set standard readonly testing device properties prior to reboot."""
195 local_props = [
196 'persist.sys.usb.config=adb',
197 'ro.monkey=1',
198 'ro.test_harness=1',
199 'ro.audio.silent=1',
200 'ro.setupwizard.mode=DISABLED',
202 if java_debug:
203 local_props.append(
204 '%s=all' % device_utils.DeviceUtils.JAVA_ASSERT_PROPERTY)
205 local_props.append('debug.checkjni=1')
206 try:
207 device.WriteFile(
208 constants.DEVICE_LOCAL_PROPERTIES_PATH,
209 '\n'.join(local_props), as_root=True)
210 # Android will not respect the local props file if it is world writable.
211 device.RunShellCommand(
212 ['chmod', '644', constants.DEVICE_LOCAL_PROPERTIES_PATH],
213 as_root=True, check_return=True)
214 except device_errors.CommandFailedError:
215 logging.exception('Failed to configure local properties.')
218 def FinishProvisioning(device, options):
219 if options.min_battery_level is not None:
220 try:
221 battery = battery_utils.BatteryUtils(device)
222 battery.ChargeDeviceToLevel(options.min_battery_level)
223 except device_errors.CommandFailedError:
224 logging.exception('Unable to charge device to specified level.')
226 if options.max_battery_temp is not None:
227 try:
228 battery = battery_utils.BatteryUtils(device)
229 battery.LetBatteryCoolToTemperature(options.max_battery_temp)
230 except device_errors.CommandFailedError:
231 logging.exception('Unable to let battery cool to specified temperature.')
233 device.RunShellCommand(
234 ['date', '-s', time.strftime('%Y%m%d.%H%M%S', time.gmtime())],
235 as_root=True, check_return=True)
236 props = device.RunShellCommand('getprop', check_return=True)
237 for prop in props:
238 logging.info(' %s' % prop)
239 if options.auto_reconnect:
240 _PushAndLaunchAdbReboot(device, options.target)
243 def _PushAndLaunchAdbReboot(device, target):
244 """Pushes and launches the adb_reboot binary on the device.
246 Arguments:
247 device: The DeviceUtils instance for the device to which the adb_reboot
248 binary should be pushed.
249 target: The build target (example, Debug or Release) which helps in
250 locating the adb_reboot binary.
252 logging.info('Will push and launch adb_reboot on %s' % str(device))
253 # Kill if adb_reboot is already running.
254 device.KillAll('adb_reboot', blocking=True, timeout=2, quiet=True)
255 # Push adb_reboot
256 logging.info(' Pushing adb_reboot ...')
257 adb_reboot = os.path.join(constants.DIR_SOURCE_ROOT,
258 'out/%s/adb_reboot' % target)
259 device.PushChangedFiles([(adb_reboot, '/data/local/tmp/')])
260 # Launch adb_reboot
261 logging.info(' Launching adb_reboot ...')
262 device.RunShellCommand(
263 ['/data/local/tmp/adb_reboot'],
264 check_return=True)
267 def _LaunchHostHeartbeat():
268 # Kill if existing host_heartbeat
269 KillHostHeartbeat()
270 # Launch a new host_heartbeat
271 logging.info('Spawning host heartbeat...')
272 subprocess.Popen([os.path.join(constants.DIR_SOURCE_ROOT,
273 'build/android/host_heartbeat.py')])
276 def KillHostHeartbeat():
277 ps = subprocess.Popen(['ps', 'aux'], stdout=subprocess.PIPE)
278 stdout, _ = ps.communicate()
279 matches = re.findall('\\n.*host_heartbeat.*', stdout)
280 for match in matches:
281 logging.info('An instance of host heart beart running... will kill')
282 pid = re.findall(r'(\S+)', match)[1]
283 subprocess.call(['kill', str(pid)])
286 def main():
287 # Recommended options on perf bots:
288 # --disable-network
289 # TODO(tonyg): We eventually want network on. However, currently radios
290 # can cause perfbots to drain faster than they charge.
291 # --min-battery-level 95
292 # Some perf bots run benchmarks with USB charging disabled which leads
293 # to gradual draining of the battery. We must wait for a full charge
294 # before starting a run in order to keep the devices online.
296 parser = argparse.ArgumentParser(
297 description='Provision Android devices with settings required for bots.')
298 parser.add_argument('-d', '--device', metavar='SERIAL',
299 help='the serial number of the device to be provisioned'
300 ' (the default is to provision all devices attached)')
301 parser.add_argument('--phase', action='append', choices=_PHASES.ALL,
302 dest='phases',
303 help='Phases of provisioning to run. '
304 '(If omitted, all phases will be run.)')
305 parser.add_argument('--skip-wipe', action='store_true', default=False,
306 help="don't wipe device data during provisioning")
307 parser.add_argument('--reboot-timeout', metavar='SECS', type=int,
308 help='when wiping the device, max number of seconds to'
309 ' wait after each reboot '
310 '(default: %s)' % _DEFAULT_TIMEOUTS.HELP_TEXT)
311 parser.add_argument('--min-battery-level', type=int, metavar='NUM',
312 help='wait for the device to reach this minimum battery'
313 ' level before trying to continue')
314 parser.add_argument('--disable-location', action='store_true',
315 help='disable Google location services on devices')
316 parser.add_argument('--disable-mock-location', action='store_true',
317 default=False, help='Set ALLOW_MOCK_LOCATION to false')
318 parser.add_argument('--disable-network', action='store_true',
319 help='disable network access on devices')
320 parser.add_argument('--disable-java-debug', action='store_false',
321 dest='enable_java_debug', default=True,
322 help='disable Java property asserts and JNI checking')
323 parser.add_argument('-t', '--target', default='Debug',
324 help='the build target (default: %(default)s)')
325 parser.add_argument('-r', '--auto-reconnect', action='store_true',
326 help='push binary which will reboot the device on adb'
327 ' disconnections')
328 parser.add_argument('--adb-key-files', type=str, nargs='+',
329 help='list of adb keys to push to device')
330 parser.add_argument('-v', '--verbose', action='count', default=1,
331 help='Log more information.')
332 parser.add_argument('--max-battery-temp', type=int, metavar='NUM',
333 help='Wait for the battery to have this temp or lower.')
334 parser.add_argument('--output-device-blacklist',
335 help='Json file to output the device blacklist.')
336 args = parser.parse_args()
337 constants.SetBuildType(args.target)
339 run_tests_helper.SetLogLevel(args.verbose)
341 return ProvisionDevices(args)
344 if __name__ == '__main__':
345 sys.exit(main())