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>]
22 from pylib
import android_commands
23 from pylib
import constants
24 from pylib
import device_settings
25 from pylib
.device
import device_blacklist
26 from pylib
.device
import device_errors
27 from pylib
.device
import device_utils
28 from pylib
.utils
import run_tests_helper
29 from pylib
.utils
import timeout_retry
31 sys
.path
.append(os
.path
.join(constants
.DIR_SOURCE_ROOT
,
32 'third_party', 'android_testrunner'))
36 class _DEFAULT_TIMEOUTS(object):
37 # L can take a while to reboot after a wipe.
41 HELP_TEXT
= '{}s on L, {}s on pre-L'.format(LOLLIPOP
, PRE_LOLLIPOP
)
44 def KillHostHeartbeat():
45 ps
= subprocess
.Popen(['ps', 'aux'], stdout
=subprocess
.PIPE
)
46 stdout
, _
= ps
.communicate()
47 matches
= re
.findall('\\n.*host_heartbeat.*', stdout
)
49 logging
.info('An instance of host heart beart running... will kill')
50 pid
= re
.findall(r
'(\S+)', match
)[1]
51 subprocess
.call(['kill', str(pid
)])
54 def LaunchHostHeartbeat():
55 # Kill if existing host_heartbeat
57 # Launch a new host_heartbeat
58 logging
.info('Spawning host heartbeat...')
59 subprocess
.Popen([os
.path
.join(constants
.DIR_SOURCE_ROOT
,
60 'build/android/host_heartbeat.py')])
63 def PushAndLaunchAdbReboot(device
, target
):
64 """Pushes and launches the adb_reboot binary on the device.
67 device: The DeviceUtils instance for the device to which the adb_reboot
68 binary should be pushed.
69 target: The build target (example, Debug or Release) which helps in
70 locating the adb_reboot binary.
72 logging
.info('Will push and launch adb_reboot on %s' % str(device
))
73 # Kill if adb_reboot is already running.
75 # Don't try to kill adb_reboot more than once. We don't expect it to be
77 device
.KillAll('adb_reboot', blocking
=True, timeout
=2, retries
=0)
78 except device_errors
.CommandFailedError
:
79 # We can safely ignore the exception because we don't expect adb_reboot
83 logging
.info(' Pushing adb_reboot ...')
84 adb_reboot
= os
.path
.join(constants
.DIR_SOURCE_ROOT
,
85 'out/%s/adb_reboot' % target
)
86 device
.PushChangedFiles([(adb_reboot
, '/data/local/tmp/')])
88 logging
.info(' Launching adb_reboot ...')
89 device
.RunShellCommand([
90 device
.GetDevicePieWrapper(),
91 '/data/local/tmp/adb_reboot'])
94 def _ConfigureLocalProperties(device
, java_debug
=True):
95 """Set standard readonly testing device properties prior to reboot."""
97 'persist.sys.usb.config=adb',
101 'ro.setupwizard.mode=DISABLED',
104 local_props
.append('%s=all' % android_commands
.JAVA_ASSERT_PROPERTY
)
105 local_props
.append('debug.checkjni=1')
108 constants
.DEVICE_LOCAL_PROPERTIES_PATH
,
109 '\n'.join(local_props
), as_root
=True)
110 # Android will not respect the local props file if it is world writable.
111 device
.RunShellCommand(
112 ['chmod', '644', constants
.DEVICE_LOCAL_PROPERTIES_PATH
],
114 except device_errors
.CommandFailedError
as e
:
115 logging
.warning(str(e
))
117 # LOCAL_PROPERTIES_PATH = '/data/local.prop'
119 def WriteAdbKeysFile(device
, adb_keys_string
):
120 dir_path
= posixpath
.dirname(constants
.ADB_KEYS_FILE
)
121 device
.RunShellCommand('mkdir -p %s' % dir_path
, as_root
=True)
122 device
.RunShellCommand('restorecon %s' % dir_path
, as_root
=True)
123 device
.WriteFile(constants
.ADB_KEYS_FILE
, adb_keys_string
, as_root
=True)
124 device
.RunShellCommand('restorecon %s' % constants
.ADB_KEYS_FILE
,
128 def WipeDeviceData(device
, options
):
129 """Wipes data from device, keeping only the adb_keys for authorization.
131 After wiping data on a device that has been authorized, adb can still
132 communicate with the device, but after reboot the device will need to be
133 re-authorized because the adb keys file is stored in /data/misc/adb/.
134 Thus, adb_keys file is rewritten so the device does not need to be
138 device: the device to wipe
140 device_authorized
= device
.FileExists(constants
.ADB_KEYS_FILE
)
141 if device_authorized
:
142 adb_keys
= device
.ReadFile(constants
.ADB_KEYS_FILE
,
143 as_root
=True).splitlines()
144 device
.RunShellCommand('wipe data', as_root
=True)
145 if device_authorized
:
146 adb_keys_set
= set(adb_keys
)
147 for adb_key_file
in options
.adb_key_files
or []:
149 with
open(adb_key_file
, 'r') as f
:
150 adb_public_keys
= f
.readlines()
151 adb_keys_set
.update(adb_public_keys
)
153 logging
.warning('Unable to find adb keys file %s.' % adb_key_file
)
154 WriteAdbKeysFile(device
, '\n'.join(adb_keys_set
))
157 def WipeDeviceIfPossible(device
, timeout
, options
):
160 WipeDeviceData(device
, options
)
161 device
.Reboot(True, timeout
=timeout
, retries
=0)
162 except (errors
.DeviceUnresponsiveError
, device_errors
.CommandFailedError
):
166 def ChargeDeviceToLevel(device
, level
):
167 def device_charged():
168 battery_level
= device
.GetBatteryInfo().get('level')
169 if battery_level
is None:
170 logging
.warning('Unable to find current battery level.')
173 logging
.info('current battery level: %d', battery_level
)
174 battery_level
= int(battery_level
)
175 return battery_level
>= level
177 timeout_retry
.WaitFor(device_charged
, wait_period
=60)
180 def ProvisionDevice(device
, options
):
181 if options
.reboot_timeout
:
182 reboot_timeout
= options
.reboot_timeout
183 elif (device
.build_version_sdk
>=
184 constants
.ANDROID_SDK_VERSION_CODES
.LOLLIPOP
):
185 reboot_timeout
= _DEFAULT_TIMEOUTS
.LOLLIPOP
187 reboot_timeout
= _DEFAULT_TIMEOUTS
.PRE_LOLLIPOP
190 if not options
.skip_wipe
:
191 WipeDeviceIfPossible(device
, reboot_timeout
, options
)
194 except device_errors
.CommandFailedError
as e
:
195 logging
.warning(str(e
))
196 _ConfigureLocalProperties(device
, options
.enable_java_debug
)
197 device_settings
.ConfigureContentSettings(
198 device
, device_settings
.DETERMINISTIC_DEVICE_SETTINGS
)
199 if options
.disable_location
:
200 device_settings
.ConfigureContentSettings(
201 device
, device_settings
.DISABLE_LOCATION_SETTINGS
)
203 device_settings
.ConfigureContentSettings(
204 device
, device_settings
.ENABLE_LOCATION_SETTINGS
)
205 device_settings
.SetLockScreenSettings(device
)
206 if options
.disable_network
:
207 device_settings
.ConfigureContentSettings(
208 device
, device_settings
.NETWORK_DISABLED_SETTINGS
)
209 if options
.min_battery_level
is not None:
211 device
.SetCharging(True)
212 ChargeDeviceToLevel(device
, options
.min_battery_level
)
213 except device_errors
.CommandFailedError
as e
:
214 logging
.exception('Unable to charge device to specified level.')
216 if not options
.skip_wipe
:
217 device
.Reboot(True, timeout
=reboot_timeout
, retries
=0)
218 device
.RunShellCommand('date -s %s' % time
.strftime('%Y%m%d.%H%M%S',
221 props
= device
.RunShellCommand('getprop')
223 logging
.info(' %s' % prop
)
224 if options
.auto_reconnect
:
225 PushAndLaunchAdbReboot(device
, options
.target
)
226 except (errors
.WaitForResponseTimedOutError
,
227 device_errors
.CommandTimeoutError
):
228 logging
.info('Timed out waiting for device %s. Adding to blacklist.',
230 # Device black list is reset by bb_device_status_check.py per build.
231 device_blacklist
.ExtendBlacklist([str(device
)])
232 except device_errors
.CommandFailedError
:
233 logging
.exception('Failed to provision device %s. Adding to blacklist.',
235 device_blacklist
.ExtendBlacklist([str(device
)])
238 def ProvisionDevices(options
):
239 if options
.device
is not None:
240 devices
= [options
.device
]
242 devices
= android_commands
.GetAttachedDevices()
244 parallel_devices
= device_utils
.DeviceUtils
.parallel(devices
)
245 parallel_devices
.pMap(ProvisionDevice
, options
)
246 if options
.auto_reconnect
:
247 LaunchHostHeartbeat()
248 blacklist
= device_blacklist
.ReadBlacklist()
249 if all(d
in blacklist
for d
in devices
):
250 raise device_errors
.NoDevicesError
255 custom_handler
= logging
.StreamHandler(sys
.stdout
)
256 custom_handler
.setFormatter(run_tests_helper
.CustomFormatter())
257 logging
.getLogger().addHandler(custom_handler
)
258 logging
.getLogger().setLevel(logging
.INFO
)
260 # Recommended options on perf bots:
262 # TODO(tonyg): We eventually want network on. However, currently radios
263 # can cause perfbots to drain faster than they charge.
264 # --min-battery-level 95
265 # Some perf bots run benchmarks with USB charging disabled which leads
266 # to gradual draining of the battery. We must wait for a full charge
267 # before starting a run in order to keep the devices online.
269 parser
= argparse
.ArgumentParser(
270 description
='Provision Android devices with settings required for bots.')
271 parser
.add_argument('-d', '--device', metavar
='SERIAL',
272 help='the serial number of the device to be provisioned'
273 ' (the default is to provision all devices attached)')
274 parser
.add_argument('--skip-wipe', action
='store_true', default
=False,
275 help="don't wipe device data during provisioning")
276 parser
.add_argument('--reboot-timeout', metavar
='SECS', type=int,
277 help='when wiping the device, max number of seconds to'
278 ' wait after each reboot '
279 '(default: %s)' % _DEFAULT_TIMEOUTS
.HELP_TEXT
)
280 parser
.add_argument('--min-battery-level', type=int, metavar
='NUM',
281 help='wait for the device to reach this minimum battery'
282 ' level before trying to continue')
283 parser
.add_argument('--disable-location', action
='store_true',
284 help='disable Google location services on devices')
285 parser
.add_argument('--disable-network', action
='store_true',
286 help='disable network access on devices')
287 parser
.add_argument('--disable-java-debug', action
='store_false',
288 dest
='enable_java_debug', default
=True,
289 help='disable Java property asserts and JNI checking')
290 parser
.add_argument('-t', '--target', default
='Debug',
291 help='the build target (default: %(default)s)')
292 parser
.add_argument('-r', '--auto-reconnect', action
='store_true',
293 help='push binary which will reboot the device on adb'
295 parser
.add_argument('--adb-key-files', type=str, nargs
='+',
296 help='list of adb keys to push to device')
297 args
= parser
.parse_args()
298 constants
.SetBuildType(args
.target
)
300 return ProvisionDevices(args
)
303 if __name__
== '__main__':