blob: e17eec324ca9dc402fd0ea6c07eba2651cd72404 [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
navabi4bcff9e2015-08-24 16:41:3114import datetime
mikecaseeb1cfbbe2015-06-29 23:31:2015import json
[email protected]552df572014-02-26 20:31:3516import logging
[email protected]12f36c82013-03-29 06:21:1317import os
navabi00c5fd32015-02-26 22:06:4718import posixpath
[email protected]12f36c82013-03-29 06:21:1319import re
20import subprocess
21import sys
22import time
23
[email protected]12f36c82013-03-29 06:21:1324from pylib import constants
[email protected]552df572014-02-26 20:31:3525from pylib import device_settings
rnephew58368d3e2015-04-06 20:02:3826from pylib.device import battery_utils
[email protected]021265ed2014-08-07 00:09:0727from pylib.device import device_blacklist
[email protected]402e54cd2014-06-25 05:09:0628from pylib.device import device_errors
[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
perezju39b8fbd62015-07-17 11:06:3933_SYSTEM_WEBVIEW_PATHS = ['/system/app/webview', '/system/app/WebViewGoogle']
mlliu2f87a552015-08-25 18:21:3534_CHROME_PACKAGE_REGEX = re.compile('.*chrom.*')
35_TOMBSTONE_REGEX = re.compile('tombstone.*')
perezju39b8fbd62015-07-17 11:06:3936
37
jbudorickb22b9622015-01-29 00:26:2138class _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
jbudorick0dfa60a2015-08-19 04:21:0054def ProvisionDevices(options):
55 devices = device_utils.DeviceUtils.HealthyDevices()
56 if options.device:
57 devices = [d for d in devices if d == options.device]
jbudorickac496302b2015-05-14 22:52:1158 if not devices:
jbudorick0dfa60a2015-08-19 04:21:0059 raise device_errors.DeviceUnreachableError(options.device)
jbudorick7f46f7bf2015-04-08 22:57:3960
61 parallel_devices = device_utils.DeviceUtils.parallel(devices)
jbudorick0dfa60a2015-08-19 04:21:0062 parallel_devices.pMap(ProvisionDevice, options)
63 if options.auto_reconnect:
jbudorick7f46f7bf2015-04-08 22:57:3964 _LaunchHostHeartbeat()
jbudorick0dfa60a2015-08-19 04:21:0065 blacklist = device_blacklist.ReadBlacklist()
66 if options.output_device_blacklist:
67 with open(options.output_device_blacklist, 'w') as f:
68 json.dump(blacklist, f)
69 if all(d in blacklist for d in devices):
jbudorick7f46f7bf2015-04-08 22:57:3970 raise device_errors.NoDevicesError
71 return 0
[email protected]12f36c82013-03-29 06:21:1372
73
jbudorick0dfa60a2015-08-19 04:21:0074def ProvisionDevice(device, options):
jbudorick7f46f7bf2015-04-08 22:57:3975 if options.reboot_timeout:
76 reboot_timeout = options.reboot_timeout
77 elif (device.build_version_sdk >=
78 constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP):
79 reboot_timeout = _DEFAULT_TIMEOUTS.LOLLIPOP
80 else:
81 reboot_timeout = _DEFAULT_TIMEOUTS.PRE_LOLLIPOP
82
83 def should_run_phase(phase_name):
84 return not options.phases or phase_name in options.phases
85
86 def run_phase(phase_func, reboot=True):
perezju386e0222015-07-16 15:26:0187 try:
88 device.WaitUntilFullyBooted(timeout=reboot_timeout, retries=0)
89 except device_errors.CommandTimeoutError:
90 logging.error('Device did not finish booting. Will try to reboot.')
91 device.Reboot(timeout=reboot_timeout)
jbudorick7f46f7bf2015-04-08 22:57:3992 phase_func(device, options)
93 if reboot:
94 device.Reboot(False, retries=0)
95 device.adb.WaitForDevice()
96
jbudorick9e8fa5da2015-04-09 23:40:2597 try:
98 if should_run_phase(_PHASES.WIPE):
mlliu2f87a552015-08-25 18:21:3599 if options.chrome_specific_wipe:
100 run_phase(WipeChromeData)
101 else:
102 run_phase(WipeDevice)
jbudorick7f46f7bf2015-04-08 22:57:39103
jbudorick9e8fa5da2015-04-09 23:40:25104 if should_run_phase(_PHASES.PROPERTIES):
105 run_phase(SetProperties)
jbudorick7f46f7bf2015-04-08 22:57:39106
jbudorick9e8fa5da2015-04-09 23:40:25107 if should_run_phase(_PHASES.FINISH):
108 run_phase(FinishProvisioning, reboot=False)
109
jbudorickcd661e4f2015-08-13 19:33:54110 except device_errors.CommandTimeoutError:
jbudorick9e8fa5da2015-04-09 23:40:25111 logging.exception('Timed out waiting for device %s. Adding to blacklist.',
112 str(device))
jbudorick0dfa60a2015-08-19 04:21:00113 device_blacklist.ExtendBlacklist([str(device)])
jbudorick9e8fa5da2015-04-09 23:40:25114
115 except device_errors.CommandFailedError:
116 logging.exception('Failed to provision device %s. Adding to blacklist.',
117 str(device))
jbudorick0dfa60a2015-08-19 04:21:00118 device_blacklist.ExtendBlacklist([str(device)])
jbudorick7f46f7bf2015-04-08 22:57:39119
120
mlliu2f87a552015-08-25 18:21:35121def WipeChromeData(device, options):
122 """Wipes chrome specific data from device
123 Chrome specific data is:
124 (1) any dir under /data/data/ whose name matches *chrom*, except
125 com.android.chrome, which is the chrome stable package
126 (2) any dir under /data/app/ and /data/app-lib/ whose name matches *chrom*
127 (3) any files under /data/tombstones/ whose name matches "tombstone*"
128 (4) /data/local.prop if there is any
129 (5) /data/local/chrome-command-line if there is any
130 (6) dir /data/local/.config/ if there is any (this is telemetry related)
131 (7) dir /data/local/tmp/
132
133 Arguments:
134 device: the device to wipe
135 """
136 if options.skip_wipe:
137 return
138
139 try:
140 device.EnableRoot()
141 _WipeUnderDirIfMatch(device, '/data/data/', _CHROME_PACKAGE_REGEX,
142 constants.PACKAGE_INFO['chrome_stable'].package)
143 _WipeUnderDirIfMatch(device, '/data/app/', _CHROME_PACKAGE_REGEX)
144 _WipeUnderDirIfMatch(device, '/data/app-lib/', _CHROME_PACKAGE_REGEX)
145 _WipeUnderDirIfMatch(device, '/data/tombstones/', _TOMBSTONE_REGEX)
146
147 _WipeFileOrDir(device, '/data/local.prop')
148 _WipeFileOrDir(device, '/data/local/chrome-command-line')
149 _WipeFileOrDir(device, '/data/local/.config/')
150 _WipeFileOrDir(device, '/data/local/tmp/')
151
152 device.RunShellCommand('rm -rf %s/*' % device.GetExternalStoragePath(),
153 check_return=True)
154 except device_errors.CommandFailedError:
155 logging.exception('Possible failure while wiping the device. '
156 'Attempting to continue.')
157
158
jbudorick7f46f7bf2015-04-08 22:57:39159def WipeDevice(device, options):
160 """Wipes data from device, keeping only the adb_keys for authorization.
161
162 After wiping data on a device that has been authorized, adb can still
163 communicate with the device, but after reboot the device will need to be
164 re-authorized because the adb keys file is stored in /data/misc/adb/.
165 Thus, adb_keys file is rewritten so the device does not need to be
166 re-authorized.
167
168 Arguments:
169 device: the device to wipe
170 """
171 if options.skip_wipe:
172 return
173
jbudorick7f46f7bf2015-04-08 22:57:39174 try:
jbudorick9e8fa5da2015-04-09 23:40:25175 device.EnableRoot()
176 device_authorized = device.FileExists(constants.ADB_KEYS_FILE)
177 if device_authorized:
178 adb_keys = device.ReadFile(constants.ADB_KEYS_FILE,
179 as_root=True).splitlines()
jbudorick7f46f7bf2015-04-08 22:57:39180 device.RunShellCommand(['wipe', 'data'],
jbudorick809d9aff2015-04-09 16:45:29181 as_root=True, check_return=True)
jbudorick9e8fa5da2015-04-09 23:40:25182 device.adb.WaitForDevice()
183
184 if device_authorized:
185 adb_keys_set = set(adb_keys)
186 for adb_key_file in options.adb_key_files or []:
187 try:
188 with open(adb_key_file, 'r') as f:
189 adb_public_keys = f.readlines()
190 adb_keys_set.update(adb_public_keys)
191 except IOError:
192 logging.warning('Unable to find adb keys file %s.' % adb_key_file)
193 _WriteAdbKeysFile(device, '\n'.join(adb_keys_set))
jbudorick7f46f7bf2015-04-08 22:57:39194 except device_errors.CommandFailedError:
195 logging.exception('Possible failure while wiping the device. '
196 'Attempting to continue.')
jbudorick7f46f7bf2015-04-08 22:57:39197
198
199def _WriteAdbKeysFile(device, adb_keys_string):
200 dir_path = posixpath.dirname(constants.ADB_KEYS_FILE)
201 device.RunShellCommand(['mkdir', '-p', dir_path],
202 as_root=True, check_return=True)
203 device.RunShellCommand(['restorecon', dir_path],
204 as_root=True, check_return=True)
205 device.WriteFile(constants.ADB_KEYS_FILE, adb_keys_string, as_root=True)
206 device.RunShellCommand(['restorecon', constants.ADB_KEYS_FILE],
207 as_root=True, check_return=True)
208
209
210def SetProperties(device, options):
211 try:
212 device.EnableRoot()
213 except device_errors.CommandFailedError as e:
214 logging.warning(str(e))
215
216 _ConfigureLocalProperties(device, options.enable_java_debug)
217 device_settings.ConfigureContentSettings(
218 device, device_settings.DETERMINISTIC_DEVICE_SETTINGS)
219 if options.disable_location:
220 device_settings.ConfigureContentSettings(
221 device, device_settings.DISABLE_LOCATION_SETTINGS)
222 else:
223 device_settings.ConfigureContentSettings(
224 device, device_settings.ENABLE_LOCATION_SETTINGS)
hush252db182015-07-08 21:42:00225
hush4143b7082015-07-09 18:41:11226 if options.disable_mock_location:
hush252db182015-07-08 21:42:00227 device_settings.ConfigureContentSettings(
228 device, device_settings.DISABLE_MOCK_LOCATION_SETTINGS)
hush4143b7082015-07-09 18:41:11229 else:
230 device_settings.ConfigureContentSettings(
231 device, device_settings.ENABLE_MOCK_LOCATION_SETTINGS)
hush252db182015-07-08 21:42:00232
jbudorick7f46f7bf2015-04-08 22:57:39233 device_settings.SetLockScreenSettings(device)
234 if options.disable_network:
235 device_settings.ConfigureContentSettings(
236 device, device_settings.NETWORK_DISABLED_SETTINGS)
237
perezju39b8fbd62015-07-17 11:06:39238 if options.remove_system_webview:
239 if device.HasRoot():
240 # This is required, e.g., to replace the system webview on a device.
241 device.adb.Remount()
242 device.RunShellCommand(['stop'], check_return=True)
243 device.RunShellCommand(['rm', '-rf'] + _SYSTEM_WEBVIEW_PATHS,
244 check_return=True)
245 device.RunShellCommand(['start'], check_return=True)
246 else:
247 logging.warning('Cannot remove system webview from a non-rooted device')
248
249
jbudorick7f46f7bf2015-04-08 22:57:39250def _ConfigureLocalProperties(device, java_debug=True):
251 """Set standard readonly testing device properties prior to reboot."""
252 local_props = [
253 'persist.sys.usb.config=adb',
254 'ro.monkey=1',
255 'ro.test_harness=1',
256 'ro.audio.silent=1',
257 'ro.setupwizard.mode=DISABLED',
258 ]
259 if java_debug:
jbudorickbfffb22e2015-04-16 14:10:02260 local_props.append(
261 '%s=all' % device_utils.DeviceUtils.JAVA_ASSERT_PROPERTY)
jbudorick7f46f7bf2015-04-08 22:57:39262 local_props.append('debug.checkjni=1')
263 try:
264 device.WriteFile(
265 constants.DEVICE_LOCAL_PROPERTIES_PATH,
266 '\n'.join(local_props), as_root=True)
267 # Android will not respect the local props file if it is world writable.
268 device.RunShellCommand(
269 ['chmod', '644', constants.DEVICE_LOCAL_PROPERTIES_PATH],
270 as_root=True, check_return=True)
jbudorick9e8fa5da2015-04-09 23:40:25271 except device_errors.CommandFailedError:
272 logging.exception('Failed to configure local properties.')
jbudorick7f46f7bf2015-04-08 22:57:39273
274
275def FinishProvisioning(device, options):
jbudorickf9f6af82015-07-01 17:24:54276 if options.min_battery_level is not None:
277 try:
278 battery = battery_utils.BatteryUtils(device)
279 battery.ChargeDeviceToLevel(options.min_battery_level)
280 except device_errors.CommandFailedError:
281 logging.exception('Unable to charge device to specified level.')
282
283 if options.max_battery_temp is not None:
284 try:
285 battery = battery_utils.BatteryUtils(device)
286 battery.LetBatteryCoolToTemperature(options.max_battery_temp)
287 except device_errors.CommandFailedError:
288 logging.exception('Unable to let battery cool to specified temperature.')
289
navabi4bcff9e2015-08-24 16:41:31290 def _set_and_verify_date():
291 strgmtime = time.strftime('%Y%m%d.%H%M%S', time.gmtime())
292 device.RunShellCommand(['date', '-s', strgmtime], as_root=True,
293 check_return=True)
294
295 device_time = device.RunShellCommand(
296 ['date', '+"%Y%m%d.%H%M%S"'], as_root=True,
297 single_line=True).replace('"', '')
298 correct_time = datetime.datetime.strptime(strgmtime,"%Y%m%d.%H%M%S")
299 tdelta = (correct_time - datetime.datetime.strptime(device_time,
300 "%Y%m%d.%H%M%S")).seconds
301 if tdelta <= 1:
302 logging.info('Date/time successfully set on %s' % device)
303 return True
304 else:
305 return False
306
307 # Sometimes the date is not set correctly on the devices. Retry on failure.
308 if not timeout_retry.WaitFor(_set_and_verify_date, wait_period=1,
309 max_tries=2):
310 logging.warning('Error setting time on device %s.', device)
311 device_blacklist.ExtendBlacklist([str(device)])
312
jbudorick9e8fa5da2015-04-09 23:40:25313 props = device.RunShellCommand('getprop', check_return=True)
314 for prop in props:
315 logging.info(' %s' % prop)
316 if options.auto_reconnect:
317 _PushAndLaunchAdbReboot(device, options.target)
jbudorick7f46f7bf2015-04-08 22:57:39318
319
mlliu2f87a552015-08-25 18:21:35320def _WipeUnderDirIfMatch(device, path, pattern, app_to_keep=None):
321 ls_result = device.Ls(path)
322 for (content, _) in ls_result:
323 if pattern.match(content):
324 if content != app_to_keep:
325 _WipeFileOrDir(device, path + content)
326
327
328def _WipeFileOrDir(device, path):
329 if device.PathExists(path):
330 device.RunShellCommand(['rm', '-rf', path], check_return=True)
331
332
jbudorick7f46f7bf2015-04-08 22:57:39333def _PushAndLaunchAdbReboot(device, target):
[email protected]c89aae02013-04-06 00:25:19334 """Pushes and launches the adb_reboot binary on the device.
335
336 Arguments:
[email protected]af4457b2014-08-21 04:17:07337 device: The DeviceUtils instance for the device to which the adb_reboot
338 binary should be pushed.
339 target: The build target (example, Debug or Release) which helps in
340 locating the adb_reboot binary.
[email protected]c89aae02013-04-06 00:25:19341 """
[email protected]af4457b2014-08-21 04:17:07342 logging.info('Will push and launch adb_reboot on %s' % str(device))
343 # Kill if adb_reboot is already running.
perezju84436c82015-04-15 10:19:22344 device.KillAll('adb_reboot', blocking=True, timeout=2, quiet=True)
[email protected]af4457b2014-08-21 04:17:07345 # Push adb_reboot
346 logging.info(' Pushing adb_reboot ...')
347 adb_reboot = os.path.join(constants.DIR_SOURCE_ROOT,
348 'out/%s/adb_reboot' % target)
jbudorick8c07d892014-10-13 11:39:48349 device.PushChangedFiles([(adb_reboot, '/data/local/tmp/')])
[email protected]af4457b2014-08-21 04:17:07350 # Launch adb_reboot
351 logging.info(' Launching adb_reboot ...')
jbudorick7f46f7bf2015-04-08 22:57:39352 device.RunShellCommand(
aurimas59c81172015-05-11 19:49:51353 ['/data/local/tmp/adb_reboot'],
jbudorick7f46f7bf2015-04-08 22:57:39354 check_return=True)
[email protected]c89aae02013-04-06 00:25:19355
356
jbudorick7f46f7bf2015-04-08 22:57:39357def _LaunchHostHeartbeat():
358 # Kill if existing host_heartbeat
jbudorick809d9aff2015-04-09 16:45:29359 KillHostHeartbeat()
jbudorick7f46f7bf2015-04-08 22:57:39360 # Launch a new host_heartbeat
361 logging.info('Spawning host heartbeat...')
362 subprocess.Popen([os.path.join(constants.DIR_SOURCE_ROOT,
363 'build/android/host_heartbeat.py')])
navabi00c5fd32015-02-26 22:06:47364
[email protected]552df572014-02-26 20:31:35365
jbudorick809d9aff2015-04-09 16:45:29366def KillHostHeartbeat():
jbudorick7f46f7bf2015-04-08 22:57:39367 ps = subprocess.Popen(['ps', 'aux'], stdout=subprocess.PIPE)
368 stdout, _ = ps.communicate()
369 matches = re.findall('\\n.*host_heartbeat.*', stdout)
370 for match in matches:
371 logging.info('An instance of host heart beart running... will kill')
372 pid = re.findall(r'(\S+)', match)[1]
373 subprocess.call(['kill', str(pid)])
[email protected]12f36c82013-03-29 06:21:13374
375
perezjub205693d2015-01-23 11:55:42376def main():
perezjub205693d2015-01-23 11:55:42377 # Recommended options on perf bots:
378 # --disable-network
379 # TODO(tonyg): We eventually want network on. However, currently radios
380 # can cause perfbots to drain faster than they charge.
perezju545a9ce2015-02-19 13:14:20381 # --min-battery-level 95
perezjub205693d2015-01-23 11:55:42382 # Some perf bots run benchmarks with USB charging disabled which leads
383 # to gradual draining of the battery. We must wait for a full charge
384 # before starting a run in order to keep the devices online.
[email protected]12f36c82013-03-29 06:21:13385
perezjub205693d2015-01-23 11:55:42386 parser = argparse.ArgumentParser(
387 description='Provision Android devices with settings required for bots.')
388 parser.add_argument('-d', '--device', metavar='SERIAL',
389 help='the serial number of the device to be provisioned'
390 ' (the default is to provision all devices attached)')
jbudorick7f46f7bf2015-04-08 22:57:39391 parser.add_argument('--phase', action='append', choices=_PHASES.ALL,
392 dest='phases',
393 help='Phases of provisioning to run. '
394 '(If omitted, all phases will be run.)')
perezjub205693d2015-01-23 11:55:42395 parser.add_argument('--skip-wipe', action='store_true', default=False,
396 help="don't wipe device data during provisioning")
jbudorickb22b9622015-01-29 00:26:21397 parser.add_argument('--reboot-timeout', metavar='SECS', type=int,
perezjub205693d2015-01-23 11:55:42398 help='when wiping the device, max number of seconds to'
jbudorickb22b9622015-01-29 00:26:21399 ' wait after each reboot '
400 '(default: %s)' % _DEFAULT_TIMEOUTS.HELP_TEXT)
perezju545a9ce2015-02-19 13:14:20401 parser.add_argument('--min-battery-level', type=int, metavar='NUM',
402 help='wait for the device to reach this minimum battery'
403 ' level before trying to continue')
perezjub205693d2015-01-23 11:55:42404 parser.add_argument('--disable-location', action='store_true',
405 help='disable Google location services on devices')
hush4143b7082015-07-09 18:41:11406 parser.add_argument('--disable-mock-location', action='store_true',
407 default=False, help='Set ALLOW_MOCK_LOCATION to false')
perezjub205693d2015-01-23 11:55:42408 parser.add_argument('--disable-network', action='store_true',
perezjub205693d2015-01-23 11:55:42409 help='disable network access on devices')
410 parser.add_argument('--disable-java-debug', action='store_false',
perezju545a9ce2015-02-19 13:14:20411 dest='enable_java_debug', default=True,
perezjub205693d2015-01-23 11:55:42412 help='disable Java property asserts and JNI checking')
perezju39b8fbd62015-07-17 11:06:39413 parser.add_argument('--remove-system-webview', action='store_true',
414 help='Remove the system webview from devices.')
perezjub205693d2015-01-23 11:55:42415 parser.add_argument('-t', '--target', default='Debug',
416 help='the build target (default: %(default)s)')
417 parser.add_argument('-r', '--auto-reconnect', action='store_true',
418 help='push binary which will reboot the device on adb'
419 ' disconnections')
navabi00c5fd32015-02-26 22:06:47420 parser.add_argument('--adb-key-files', type=str, nargs='+',
421 help='list of adb keys to push to device')
jbudorick7f46f7bf2015-04-08 22:57:39422 parser.add_argument('-v', '--verbose', action='count', default=1,
423 help='Log more information.')
rnephew7ce719b2015-05-07 05:35:54424 parser.add_argument('--max-battery-temp', type=int, metavar='NUM',
425 help='Wait for the battery to have this temp or lower.')
mikecaseeb1cfbbe2015-06-29 23:31:20426 parser.add_argument('--output-device-blacklist',
427 help='Json file to output the device blacklist.')
mlliu2f87a552015-08-25 18:21:35428 parser.add_argument('--chrome-specific-wipe', action='store_true',
429 help='only wipe chrome specific data during provisioning')
perezjub205693d2015-01-23 11:55:42430 args = parser.parse_args()
431 constants.SetBuildType(args.target)
432
jbudorick7f46f7bf2015-04-08 22:57:39433 run_tests_helper.SetLogLevel(args.verbose)
434
perezjub205693d2015-01-23 11:55:42435 return ProvisionDevices(args)
[email protected]12f36c82013-03-29 06:21:13436
437
438if __name__ == '__main__':
perezjub205693d2015-01-23 11:55:42439 sys.exit(main())