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.
10 ./provision_devices.py [-d <device serial number>]
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'))
37 _SYSTEM_WEBVIEW_PATHS
= ['/system/app/webview', '/system/app/WebViewGoogle']
40 class _DEFAULT_TIMEOUTS(object):
41 # L can take a while to reboot after a wipe.
45 HELP_TEXT
= '{}s on L, {}s on pre-L'.format(LOLLIPOP
, PRE_LOLLIPOP
)
48 class _PHASES(object):
50 PROPERTIES
= 'properties'
53 ALL
= [WIPE
, PROPERTIES
, FINISH
]
56 def ProvisionDevices(options
):
57 devices
= device_utils
.DeviceUtils
.HealthyDevices()
59 devices
= [d
for d
in devices
if d
== options
.device
]
61 raise device_errors
.DeviceUnreachableError(options
.device
)
63 parallel_devices
= device_utils
.DeviceUtils
.parallel(devices
)
64 parallel_devices
.pMap(ProvisionDevice
, options
)
65 if options
.auto_reconnect
:
66 _LaunchHostHeartbeat()
67 blacklist
= device_blacklist
.ReadBlacklist()
68 if options
.output_device_blacklist
:
69 with
open(options
.output_device_blacklist
, 'w') as f
:
70 json
.dump(blacklist
, f
)
71 if all(d
in blacklist
for d
in devices
):
72 raise device_errors
.NoDevicesError
76 def ProvisionDevice(device
, options
):
77 if options
.reboot_timeout
:
78 reboot_timeout
= options
.reboot_timeout
79 elif (device
.build_version_sdk
>=
80 constants
.ANDROID_SDK_VERSION_CODES
.LOLLIPOP
):
81 reboot_timeout
= _DEFAULT_TIMEOUTS
.LOLLIPOP
83 reboot_timeout
= _DEFAULT_TIMEOUTS
.PRE_LOLLIPOP
85 def should_run_phase(phase_name
):
86 return not options
.phases
or phase_name
in options
.phases
88 def run_phase(phase_func
, reboot
=True):
90 device
.WaitUntilFullyBooted(timeout
=reboot_timeout
, retries
=0)
91 except device_errors
.CommandTimeoutError
:
92 logging
.error('Device did not finish booting. Will try to reboot.')
93 device
.Reboot(timeout
=reboot_timeout
)
94 phase_func(device
, options
)
96 device
.Reboot(False, retries
=0)
97 device
.adb
.WaitForDevice()
100 if should_run_phase(_PHASES
.WIPE
):
101 run_phase(WipeDevice
)
103 if should_run_phase(_PHASES
.PROPERTIES
):
104 run_phase(SetProperties
)
106 if should_run_phase(_PHASES
.FINISH
):
107 run_phase(FinishProvisioning
, reboot
=False)
109 except (errors
.WaitForResponseTimedOutError
,
110 device_errors
.CommandTimeoutError
):
111 logging
.exception('Timed out waiting for device %s. Adding to blacklist.',
113 device_blacklist
.ExtendBlacklist([str(device
)])
115 except device_errors
.CommandFailedError
:
116 logging
.exception('Failed to provision device %s. Adding to blacklist.',
118 device_blacklist
.ExtendBlacklist([str(device
)])
121 def WipeDevice(device
, options
):
122 """Wipes data from device, keeping only the adb_keys for authorization.
124 After wiping data on a device that has been authorized, adb can still
125 communicate with the device, but after reboot the device will need to be
126 re-authorized because the adb keys file is stored in /data/misc/adb/.
127 Thus, adb_keys file is rewritten so the device does not need to be
131 device: the device to wipe
133 if options
.skip_wipe
:
138 device_authorized
= device
.FileExists(constants
.ADB_KEYS_FILE
)
139 if device_authorized
:
140 adb_keys
= device
.ReadFile(constants
.ADB_KEYS_FILE
,
141 as_root
=True).splitlines()
142 device
.RunShellCommand(['wipe', 'data'],
143 as_root
=True, check_return
=True)
144 device
.adb
.WaitForDevice()
146 if device_authorized
:
147 adb_keys_set
= set(adb_keys
)
148 for adb_key_file
in options
.adb_key_files
or []:
150 with
open(adb_key_file
, 'r') as f
:
151 adb_public_keys
= f
.readlines()
152 adb_keys_set
.update(adb_public_keys
)
154 logging
.warning('Unable to find adb keys file %s.' % adb_key_file
)
155 _WriteAdbKeysFile(device
, '\n'.join(adb_keys_set
))
156 except device_errors
.CommandFailedError
:
157 logging
.exception('Possible failure while wiping the device. '
158 'Attempting to continue.')
161 def _WriteAdbKeysFile(device
, adb_keys_string
):
162 dir_path
= posixpath
.dirname(constants
.ADB_KEYS_FILE
)
163 device
.RunShellCommand(['mkdir', '-p', dir_path
],
164 as_root
=True, check_return
=True)
165 device
.RunShellCommand(['restorecon', dir_path
],
166 as_root
=True, check_return
=True)
167 device
.WriteFile(constants
.ADB_KEYS_FILE
, adb_keys_string
, as_root
=True)
168 device
.RunShellCommand(['restorecon', constants
.ADB_KEYS_FILE
],
169 as_root
=True, check_return
=True)
172 def SetProperties(device
, options
):
175 except device_errors
.CommandFailedError
as e
:
176 logging
.warning(str(e
))
178 _ConfigureLocalProperties(device
, options
.enable_java_debug
)
179 device_settings
.ConfigureContentSettings(
180 device
, device_settings
.DETERMINISTIC_DEVICE_SETTINGS
)
181 if options
.disable_location
:
182 device_settings
.ConfigureContentSettings(
183 device
, device_settings
.DISABLE_LOCATION_SETTINGS
)
185 device_settings
.ConfigureContentSettings(
186 device
, device_settings
.ENABLE_LOCATION_SETTINGS
)
188 if options
.disable_mock_location
:
189 device_settings
.ConfigureContentSettings(
190 device
, device_settings
.DISABLE_MOCK_LOCATION_SETTINGS
)
192 device_settings
.ConfigureContentSettings(
193 device
, device_settings
.ENABLE_MOCK_LOCATION_SETTINGS
)
195 device_settings
.SetLockScreenSettings(device
)
196 if options
.disable_network
:
197 device_settings
.ConfigureContentSettings(
198 device
, device_settings
.NETWORK_DISABLED_SETTINGS
)
200 if options
.remove_system_webview
:
202 # This is required, e.g., to replace the system webview on a device.
204 device
.RunShellCommand(['stop'], check_return
=True)
205 device
.RunShellCommand(['rm', '-rf'] + _SYSTEM_WEBVIEW_PATHS
,
207 device
.RunShellCommand(['start'], check_return
=True)
209 logging
.warning('Cannot remove system webview from a non-rooted device')
212 def _ConfigureLocalProperties(device
, java_debug
=True):
213 """Set standard readonly testing device properties prior to reboot."""
215 'persist.sys.usb.config=adb',
219 'ro.setupwizard.mode=DISABLED',
223 '%s=all' % device_utils
.DeviceUtils
.JAVA_ASSERT_PROPERTY
)
224 local_props
.append('debug.checkjni=1')
227 constants
.DEVICE_LOCAL_PROPERTIES_PATH
,
228 '\n'.join(local_props
), as_root
=True)
229 # Android will not respect the local props file if it is world writable.
230 device
.RunShellCommand(
231 ['chmod', '644', constants
.DEVICE_LOCAL_PROPERTIES_PATH
],
232 as_root
=True, check_return
=True)
233 except device_errors
.CommandFailedError
:
234 logging
.exception('Failed to configure local properties.')
237 def FinishProvisioning(device
, options
):
238 if options
.min_battery_level
is not None:
240 battery
= battery_utils
.BatteryUtils(device
)
241 battery
.ChargeDeviceToLevel(options
.min_battery_level
)
242 except device_errors
.CommandFailedError
:
243 logging
.exception('Unable to charge device to specified level.')
245 if options
.max_battery_temp
is not None:
247 battery
= battery_utils
.BatteryUtils(device
)
248 battery
.LetBatteryCoolToTemperature(options
.max_battery_temp
)
249 except device_errors
.CommandFailedError
:
250 logging
.exception('Unable to let battery cool to specified temperature.')
252 device
.RunShellCommand(
253 ['date', '-s', time
.strftime('%Y%m%d.%H%M%S', time
.gmtime())],
254 as_root
=True, check_return
=True)
255 props
= device
.RunShellCommand('getprop', check_return
=True)
257 logging
.info(' %s' % prop
)
258 if options
.auto_reconnect
:
259 _PushAndLaunchAdbReboot(device
, options
.target
)
262 def _PushAndLaunchAdbReboot(device
, target
):
263 """Pushes and launches the adb_reboot binary on the device.
266 device: The DeviceUtils instance for the device to which the adb_reboot
267 binary should be pushed.
268 target: The build target (example, Debug or Release) which helps in
269 locating the adb_reboot binary.
271 logging
.info('Will push and launch adb_reboot on %s' % str(device
))
272 # Kill if adb_reboot is already running.
273 device
.KillAll('adb_reboot', blocking
=True, timeout
=2, quiet
=True)
275 logging
.info(' Pushing adb_reboot ...')
276 adb_reboot
= os
.path
.join(constants
.DIR_SOURCE_ROOT
,
277 'out/%s/adb_reboot' % target
)
278 device
.PushChangedFiles([(adb_reboot
, '/data/local/tmp/')])
280 logging
.info(' Launching adb_reboot ...')
281 device
.RunShellCommand(
282 ['/data/local/tmp/adb_reboot'],
286 def _LaunchHostHeartbeat():
287 # Kill if existing host_heartbeat
289 # Launch a new host_heartbeat
290 logging
.info('Spawning host heartbeat...')
291 subprocess
.Popen([os
.path
.join(constants
.DIR_SOURCE_ROOT
,
292 'build/android/host_heartbeat.py')])
295 def KillHostHeartbeat():
296 ps
= subprocess
.Popen(['ps', 'aux'], stdout
=subprocess
.PIPE
)
297 stdout
, _
= ps
.communicate()
298 matches
= re
.findall('\\n.*host_heartbeat.*', stdout
)
299 for match
in matches
:
300 logging
.info('An instance of host heart beart running... will kill')
301 pid
= re
.findall(r
'(\S+)', match
)[1]
302 subprocess
.call(['kill', str(pid
)])
306 # Recommended options on perf bots:
308 # TODO(tonyg): We eventually want network on. However, currently radios
309 # can cause perfbots to drain faster than they charge.
310 # --min-battery-level 95
311 # Some perf bots run benchmarks with USB charging disabled which leads
312 # to gradual draining of the battery. We must wait for a full charge
313 # before starting a run in order to keep the devices online.
315 parser
= argparse
.ArgumentParser(
316 description
='Provision Android devices with settings required for bots.')
317 parser
.add_argument('-d', '--device', metavar
='SERIAL',
318 help='the serial number of the device to be provisioned'
319 ' (the default is to provision all devices attached)')
320 parser
.add_argument('--phase', action
='append', choices
=_PHASES
.ALL
,
322 help='Phases of provisioning to run. '
323 '(If omitted, all phases will be run.)')
324 parser
.add_argument('--skip-wipe', action
='store_true', default
=False,
325 help="don't wipe device data during provisioning")
326 parser
.add_argument('--reboot-timeout', metavar
='SECS', type=int,
327 help='when wiping the device, max number of seconds to'
328 ' wait after each reboot '
329 '(default: %s)' % _DEFAULT_TIMEOUTS
.HELP_TEXT
)
330 parser
.add_argument('--min-battery-level', type=int, metavar
='NUM',
331 help='wait for the device to reach this minimum battery'
332 ' level before trying to continue')
333 parser
.add_argument('--disable-location', action
='store_true',
334 help='disable Google location services on devices')
335 parser
.add_argument('--disable-mock-location', action
='store_true',
336 default
=False, help='Set ALLOW_MOCK_LOCATION to false')
337 parser
.add_argument('--disable-network', action
='store_true',
338 help='disable network access on devices')
339 parser
.add_argument('--disable-java-debug', action
='store_false',
340 dest
='enable_java_debug', default
=True,
341 help='disable Java property asserts and JNI checking')
342 parser
.add_argument('--remove-system-webview', action
='store_true',
343 help='Remove the system webview from devices.')
344 parser
.add_argument('-t', '--target', default
='Debug',
345 help='the build target (default: %(default)s)')
346 parser
.add_argument('-r', '--auto-reconnect', action
='store_true',
347 help='push binary which will reboot the device on adb'
349 parser
.add_argument('--adb-key-files', type=str, nargs
='+',
350 help='list of adb keys to push to device')
351 parser
.add_argument('-v', '--verbose', action
='count', default
=1,
352 help='Log more information.')
353 parser
.add_argument('--max-battery-temp', type=int, metavar
='NUM',
354 help='Wait for the battery to have this temp or lower.')
355 parser
.add_argument('--output-device-blacklist',
356 help='Json file to output the device blacklist.')
357 args
= parser
.parse_args()
358 constants
.SetBuildType(args
.target
)
360 run_tests_helper
.SetLogLevel(args
.verbose
)
362 return ProvisionDevices(args
)
365 if __name__
== '__main__':