Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(112)

Side by Side Diff: build/android/provision_devices.py

Issue 1060943002: [Android] Add severable device provisioning. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 5 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # 2 #
3 # Copyright (c) 2013 The Chromium Authors. All rights reserved. 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 4 # Use of this source code is governed by a BSD-style license that can be
5 # found in the LICENSE file. 5 # found in the LICENSE file.
6 6
7 """Provisions Android devices with settings required for bots. 7 """Provisions Android devices with settings required for bots.
8 8
9 Usage: 9 Usage:
10 ./provision_devices.py [-d <device serial number>] 10 ./provision_devices.py [-d <device serial number>]
(...skipping 24 matching lines...) Expand all
35 35
36 36
37 class _DEFAULT_TIMEOUTS(object): 37 class _DEFAULT_TIMEOUTS(object):
38 # L can take a while to reboot after a wipe. 38 # L can take a while to reboot after a wipe.
39 LOLLIPOP = 600 39 LOLLIPOP = 600
40 PRE_LOLLIPOP = 180 40 PRE_LOLLIPOP = 180
41 41
42 HELP_TEXT = '{}s on L, {}s on pre-L'.format(LOLLIPOP, PRE_LOLLIPOP) 42 HELP_TEXT = '{}s on L, {}s on pre-L'.format(LOLLIPOP, PRE_LOLLIPOP)
43 43
44 44
45 def KillHostHeartbeat(): 45 class _PHASES(object):
46 ps = subprocess.Popen(['ps', 'aux'], stdout=subprocess.PIPE) 46 WIPE = 'wipe'
47 stdout, _ = ps.communicate() 47 PROPERTIES = 'properties'
48 matches = re.findall('\\n.*host_heartbeat.*', stdout) 48 FINISH = 'finish'
49 for match in matches: 49
50 logging.info('An instance of host heart beart running... will kill') 50 ALL = [WIPE, PROPERTIES, FINISH]
51 pid = re.findall(r'(\S+)', match)[1]
52 subprocess.call(['kill', str(pid)])
53 51
54 52
55 def LaunchHostHeartbeat(): 53 def ProvisionDevices(options):
56 # Kill if existing host_heartbeat 54 if options.device is not None:
57 KillHostHeartbeat() 55 devices = [options.device]
navabi 2015/04/07 20:52:37 This hasn't changed right? Only moved in the scrip
jbudorick 2015/04/08 01:32:56 Yep, moved from (1)
58 # Launch a new host_heartbeat 56 else:
59 logging.info('Spawning host heartbeat...') 57 devices = android_commands.GetAttachedDevices()
60 subprocess.Popen([os.path.join(constants.DIR_SOURCE_ROOT, 58
61 'build/android/host_heartbeat.py')]) 59 parallel_devices = device_utils.DeviceUtils.parallel(devices)
60 parallel_devices.pMap(ProvisionDevice, options)
61 if options.auto_reconnect:
62 _LaunchHostHeartbeat()
63 blacklist = device_blacklist.ReadBlacklist()
64 if all(d in blacklist for d in devices):
65 raise device_errors.NoDevicesError
66 return 0
62 67
63 68
64 def PushAndLaunchAdbReboot(device, target): 69 def ProvisionDevice(device, options):
65 """Pushes and launches the adb_reboot binary on the device. 70 if options.reboot_timeout:
71 reboot_timeout = options.reboot_timeout
72 elif (device.build_version_sdk >=
73 constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP):
74 reboot_timeout = _DEFAULT_TIMEOUTS.LOLLIPOP
75 else:
76 reboot_timeout = _DEFAULT_TIMEOUTS.PRE_LOLLIPOP
66 77
67 Arguments: 78 def should_run_phase(phase_name):
68 device: The DeviceUtils instance for the device to which the adb_reboot 79 return not options.phases or phase_name in options.phases
69 binary should be pushed. 80
70 target: The build target (example, Debug or Release) which helps in 81 def run_phase(phase_func, reboot=True):
71 locating the adb_reboot binary. 82 device.WaitUntilFullyBooted(timeout=reboot_timeout)
72 """ 83 phase_func(device, options)
73 logging.info('Will push and launch adb_reboot on %s' % str(device)) 84 if reboot:
74 # Kill if adb_reboot is already running. 85 device.Reboot(False, retries=0)
jbudorick 2015/04/08 19:44:35 (3): both reboots are done here.
75 try: 86 device.adb.WaitForDevice()
76 # Don't try to kill adb_reboot more than once. We don't expect it to be 87
77 # running at all. 88 if should_run_phase(_PHASES.WIPE):
78 device.KillAll('adb_reboot', blocking=True, timeout=2, retries=0) 89 run_phase(WipeDevice)
79 except device_errors.CommandFailedError: 90
80 # We can safely ignore the exception because we don't expect adb_reboot 91 if should_run_phase(_PHASES.PROPERTIES):
81 # to be running. 92 run_phase(SetProperties)
jbudorick 2015/04/08 19:44:35 (2): The reboot that previously preceded setting t
82 pass 93
83 # Push adb_reboot 94 if should_run_phase(_PHASES.FINISH):
84 logging.info(' Pushing adb_reboot ...') 95 run_phase(FinishProvisioning, reboot=False)
85 adb_reboot = os.path.join(constants.DIR_SOURCE_ROOT,
86 'out/%s/adb_reboot' % target)
87 device.PushChangedFiles([(adb_reboot, '/data/local/tmp/')])
88 # Launch adb_reboot
89 logging.info(' Launching adb_reboot ...')
90 device.RunShellCommand([
91 device.GetDevicePieWrapper(),
92 '/data/local/tmp/adb_reboot'])
93 96
94 97
95 def _ConfigureLocalProperties(device, java_debug=True): 98 def WipeDevice(device, options):
96 """Set standard readonly testing device properties prior to reboot."""
97 local_props = [
98 'persist.sys.usb.config=adb',
99 'ro.monkey=1',
100 'ro.test_harness=1',
101 'ro.audio.silent=1',
102 'ro.setupwizard.mode=DISABLED',
103 ]
104 if java_debug:
105 local_props.append('%s=all' % android_commands.JAVA_ASSERT_PROPERTY)
106 local_props.append('debug.checkjni=1')
107 try:
108 device.WriteFile(
109 constants.DEVICE_LOCAL_PROPERTIES_PATH,
110 '\n'.join(local_props), as_root=True)
111 # Android will not respect the local props file if it is world writable.
112 device.RunShellCommand(
113 ['chmod', '644', constants.DEVICE_LOCAL_PROPERTIES_PATH],
114 as_root=True)
115 except device_errors.CommandFailedError as e:
116 logging.warning(str(e))
117
118 # LOCAL_PROPERTIES_PATH = '/data/local.prop'
119
120 def WriteAdbKeysFile(device, adb_keys_string):
121 dir_path = posixpath.dirname(constants.ADB_KEYS_FILE)
122 device.RunShellCommand('mkdir -p %s' % dir_path, as_root=True)
123 device.RunShellCommand('restorecon %s' % dir_path, as_root=True)
124 device.WriteFile(constants.ADB_KEYS_FILE, adb_keys_string, as_root=True)
125 device.RunShellCommand('restorecon %s' % constants.ADB_KEYS_FILE,
126 as_root=True)
127
128
129 def WipeDeviceData(device, options):
130 """Wipes data from device, keeping only the adb_keys for authorization. 99 """Wipes data from device, keeping only the adb_keys for authorization.
131 100
132 After wiping data on a device that has been authorized, adb can still 101 After wiping data on a device that has been authorized, adb can still
133 communicate with the device, but after reboot the device will need to be 102 communicate with the device, but after reboot the device will need to be
134 re-authorized because the adb keys file is stored in /data/misc/adb/. 103 re-authorized because the adb keys file is stored in /data/misc/adb/.
135 Thus, adb_keys file is rewritten so the device does not need to be 104 Thus, adb_keys file is rewritten so the device does not need to be
136 re-authorized. 105 re-authorized.
137 106
138 Arguments: 107 Arguments:
139 device: the device to wipe 108 device: the device to wipe
140 """ 109 """
110 if options.skip_wipe:
111 return
112
113 device.EnableRoot()
141 device_authorized = device.FileExists(constants.ADB_KEYS_FILE) 114 device_authorized = device.FileExists(constants.ADB_KEYS_FILE)
142 if device_authorized: 115 if device_authorized:
143 adb_keys = device.ReadFile(constants.ADB_KEYS_FILE, 116 adb_keys = device.ReadFile(constants.ADB_KEYS_FILE,
144 as_root=True).splitlines() 117 as_root=True).splitlines()
145 device.RunShellCommand('wipe data', as_root=True) 118 try:
119 device.RunShellCommand(['wipe', 'data'],
120 as_root=True, check_return=True, retries=0)
121 except device_errors.CommandFailedError:
122 logging.exception('Possible failure while wiping the device. '
123 'Attempting to continue.')
124 device.adb.WaitForDevice()
125
146 if device_authorized: 126 if device_authorized:
147 adb_keys_set = set(adb_keys) 127 adb_keys_set = set(adb_keys)
148 for adb_key_file in options.adb_key_files or []: 128 for adb_key_file in options.adb_key_files or []:
149 try: 129 try:
150 with open(adb_key_file, 'r') as f: 130 with open(adb_key_file, 'r') as f:
151 adb_public_keys = f.readlines() 131 adb_public_keys = f.readlines()
152 adb_keys_set.update(adb_public_keys) 132 adb_keys_set.update(adb_public_keys)
153 except IOError: 133 except IOError:
154 logging.warning('Unable to find adb keys file %s.' % adb_key_file) 134 logging.warning('Unable to find adb keys file %s.' % adb_key_file)
155 WriteAdbKeysFile(device, '\n'.join(adb_keys_set)) 135 _WriteAdbKeysFile(device, '\n'.join(adb_keys_set))
156 136
157 137
158 def WipeDeviceIfPossible(device, timeout, options): 138 def _WriteAdbKeysFile(device, adb_keys_string):
139 dir_path = posixpath.dirname(constants.ADB_KEYS_FILE)
140 device.RunShellCommand(['mkdir', '-p', dir_path],
141 as_root=True, check_return=True)
142 device.RunShellCommand(['restorecon', dir_path],
143 as_root=True, check_return=True)
144 device.WriteFile(constants.ADB_KEYS_FILE, adb_keys_string, as_root=True)
145 device.RunShellCommand(['restorecon', constants.ADB_KEYS_FILE],
146 as_root=True, check_return=True)
147
148
149 def SetProperties(device, options):
159 try: 150 try:
160 device.EnableRoot() 151 device.EnableRoot()
161 WipeDeviceData(device, options) 152 except device_errors.CommandFailedError as e:
162 device.Reboot(True, timeout=timeout, retries=0) 153 logging.warning(str(e))
163 except (errors.DeviceUnresponsiveError, device_errors.CommandFailedError): 154
164 pass 155 _ConfigureLocalProperties(device, options.enable_java_debug)
156 device_settings.ConfigureContentSettings(
157 device, device_settings.DETERMINISTIC_DEVICE_SETTINGS)
158 if options.disable_location:
159 device_settings.ConfigureContentSettings(
160 device, device_settings.DISABLE_LOCATION_SETTINGS)
161 else:
162 device_settings.ConfigureContentSettings(
163 device, device_settings.ENABLE_LOCATION_SETTINGS)
164 device_settings.SetLockScreenSettings(device)
165 if options.disable_network:
166 device_settings.ConfigureContentSettings(
167 device, device_settings.NETWORK_DISABLED_SETTINGS)
168
169 if options.min_battery_level is not None:
170 try:
171 battery = battery_utils.BatteryUtils(device)
172 battery.ChargeDeviceToLevel(options.min_battery_level)
173 except device_errors.CommandFailedError as e:
174 logging.exception('Unable to charge device to specified level.')
165 175
166 176
167 def ProvisionDevice(device, options): 177 def _ConfigureLocalProperties(device, java_debug=True):
168 if options.reboot_timeout: 178 """Set standard readonly testing device properties prior to reboot."""
169 reboot_timeout = options.reboot_timeout 179 local_props = [
170 elif (device.build_version_sdk >= 180 'persist.sys.usb.config=adb',
171 constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP): 181 'ro.monkey=1',
172 reboot_timeout = _DEFAULT_TIMEOUTS.LOLLIPOP 182 'ro.test_harness=1',
173 else: 183 'ro.audio.silent=1',
174 reboot_timeout = _DEFAULT_TIMEOUTS.PRE_LOLLIPOP 184 'ro.setupwizard.mode=DISABLED',
185 ]
186 if java_debug:
187 local_props.append('%s=all' % android_commands.JAVA_ASSERT_PROPERTY)
188 local_props.append('debug.checkjni=1')
189 try:
190 device.WriteFile(
191 constants.DEVICE_LOCAL_PROPERTIES_PATH,
192 '\n'.join(local_props), as_root=True)
193 # Android will not respect the local props file if it is world writable.
194 device.RunShellCommand(
195 ['chmod', '644', constants.DEVICE_LOCAL_PROPERTIES_PATH],
196 as_root=True, check_return=True)
197 except device_errors.CommandFailedError as e:
198 logging.warning(str(e))
175 199
200
201 def FinishProvisioning(device, options):
176 try: 202 try:
177 if not options.skip_wipe: 203 device.RunShellCommand(
navabi 2015/04/07 20:52:37 Do we reboot before setting the date? I think I re
jbudorick 2015/04/08 01:32:57 This does set the date after rebooting.
178 WipeDeviceIfPossible(device, reboot_timeout, options) 204 ['date', '-s', time.strftime('%Y%m%d.%H%M%S', time.gmtime())],
179 try: 205 as_root=True, check_return=True)
180 device.EnableRoot() 206 props = device.RunShellCommand('getprop', check_return=True)
181 except device_errors.CommandFailedError as e:
182 logging.warning(str(e))
183 _ConfigureLocalProperties(device, options.enable_java_debug)
184 device_settings.ConfigureContentSettings(
185 device, device_settings.DETERMINISTIC_DEVICE_SETTINGS)
186 if options.disable_location:
187 device_settings.ConfigureContentSettings(
188 device, device_settings.DISABLE_LOCATION_SETTINGS)
189 else:
190 device_settings.ConfigureContentSettings(
191 device, device_settings.ENABLE_LOCATION_SETTINGS)
192 device_settings.SetLockScreenSettings(device)
193 if options.disable_network:
194 device_settings.ConfigureContentSettings(
195 device, device_settings.NETWORK_DISABLED_SETTINGS)
196 if options.min_battery_level is not None:
197 try:
198 battery = battery_utils.BatteryUtils(device)
199 battery.ChargeDeviceToLevel(options.min_battery_level)
200 except device_errors.CommandFailedError as e:
201 logging.exception('Unable to charge device to specified level.')
202
203 if not options.skip_wipe:
204 device.Reboot(True, timeout=reboot_timeout, retries=0)
navabi 2015/04/07 20:52:37 we reboot after setting all the properties so they
jbudorick 2015/04/08 01:32:56 Yeah, this ordering was maintained in the new vers
navabi 2015/04/08 19:39:27 Hmm. Maybe I'm just not seeing something obvious,
jbudorick 2015/04/08 19:44:35 I broke this into phases around the reboots, i.e.,
navabi 2015/04/08 19:48:47 Ah ha!! Reboot at the end of every run_phase. I li
205 device.RunShellCommand('date -s %s' % time.strftime('%Y%m%d.%H%M%S',
206 time.gmtime()),
207 as_root=True)
208 props = device.RunShellCommand('getprop')
209 for prop in props: 207 for prop in props:
210 logging.info(' %s' % prop) 208 logging.info(' %s' % prop)
211 if options.auto_reconnect: 209 if options.auto_reconnect:
212 PushAndLaunchAdbReboot(device, options.target) 210 _PushAndLaunchAdbReboot(device, options.target)
213 except (errors.WaitForResponseTimedOutError, 211 except (errors.WaitForResponseTimedOutError,
214 device_errors.CommandTimeoutError): 212 device_errors.CommandTimeoutError):
215 logging.info('Timed out waiting for device %s. Adding to blacklist.', 213 logging.info('Timed out waiting for device %s. Adding to blacklist.',
216 str(device)) 214 str(device))
217 # Device black list is reset by bb_device_status_check.py per build. 215 # Device black list is reset by bb_device_status_check.py per build.
218 device_blacklist.ExtendBlacklist([str(device)]) 216 device_blacklist.ExtendBlacklist([str(device)])
219 except device_errors.CommandFailedError: 217 except device_errors.CommandFailedError:
220 logging.exception('Failed to provision device %s. Adding to blacklist.', 218 logging.exception('Failed to provision device %s. Adding to blacklist.',
221 str(device)) 219 str(device))
222 device_blacklist.ExtendBlacklist([str(device)]) 220 device_blacklist.ExtendBlacklist([str(device)])
223 221
224 222
225 def ProvisionDevices(options): 223 def _PushAndLaunchAdbReboot(device, target):
226 if options.device is not None: 224 """Pushes and launches the adb_reboot binary on the device.
227 devices = [options.device]
jbudorick 2015/04/08 01:32:56 (1)
228 else:
229 devices = android_commands.GetAttachedDevices()
230 225
231 parallel_devices = device_utils.DeviceUtils.parallel(devices) 226 Arguments:
232 parallel_devices.pMap(ProvisionDevice, options) 227 device: The DeviceUtils instance for the device to which the adb_reboot
233 if options.auto_reconnect: 228 binary should be pushed.
234 LaunchHostHeartbeat() 229 target: The build target (example, Debug or Release) which helps in
235 blacklist = device_blacklist.ReadBlacklist() 230 locating the adb_reboot binary.
236 if all(d in blacklist for d in devices): 231 """
237 raise device_errors.NoDevicesError 232 logging.info('Will push and launch adb_reboot on %s' % str(device))
238 return 0 233 # Kill if adb_reboot is already running.
234 try:
235 # Don't try to kill adb_reboot more than once. We don't expect it to be
236 # running at all.
237 device.KillAll('adb_reboot', blocking=True, timeout=2, retries=0)
238 except device_errors.CommandFailedError:
239 # We can safely ignore the exception because we don't expect adb_reboot
240 # to be running.
241 pass
242 # Push adb_reboot
243 logging.info(' Pushing adb_reboot ...')
244 adb_reboot = os.path.join(constants.DIR_SOURCE_ROOT,
245 'out/%s/adb_reboot' % target)
246 device.PushChangedFiles([(adb_reboot, '/data/local/tmp/')])
247 # Launch adb_reboot
248 logging.info(' Launching adb_reboot ...')
249 device.RunShellCommand(
250 [device.GetDevicePieWrapper(), '/data/local/tmp/adb_reboot'],
251 check_return=True)
252
253
254 def _LaunchHostHeartbeat():
255 # Kill if existing host_heartbeat
256 _KillHostHeartbeat()
257 # Launch a new host_heartbeat
258 logging.info('Spawning host heartbeat...')
259 subprocess.Popen([os.path.join(constants.DIR_SOURCE_ROOT,
260 'build/android/host_heartbeat.py')])
261
262
263 def _KillHostHeartbeat():
264 ps = subprocess.Popen(['ps', 'aux'], stdout=subprocess.PIPE)
265 stdout, _ = ps.communicate()
266 matches = re.findall('\\n.*host_heartbeat.*', stdout)
267 for match in matches:
268 logging.info('An instance of host heart beart running... will kill')
269 pid = re.findall(r'(\S+)', match)[1]
270 subprocess.call(['kill', str(pid)])
239 271
240 272
241 def main(): 273 def main():
242 custom_handler = logging.StreamHandler(sys.stdout)
243 custom_handler.setFormatter(run_tests_helper.CustomFormatter())
244 logging.getLogger().addHandler(custom_handler)
245 logging.getLogger().setLevel(logging.INFO)
246
247 # Recommended options on perf bots: 274 # Recommended options on perf bots:
248 # --disable-network 275 # --disable-network
249 # TODO(tonyg): We eventually want network on. However, currently radios 276 # TODO(tonyg): We eventually want network on. However, currently radios
250 # can cause perfbots to drain faster than they charge. 277 # can cause perfbots to drain faster than they charge.
251 # --min-battery-level 95 278 # --min-battery-level 95
252 # Some perf bots run benchmarks with USB charging disabled which leads 279 # Some perf bots run benchmarks with USB charging disabled which leads
253 # to gradual draining of the battery. We must wait for a full charge 280 # to gradual draining of the battery. We must wait for a full charge
254 # before starting a run in order to keep the devices online. 281 # before starting a run in order to keep the devices online.
255 282
256 parser = argparse.ArgumentParser( 283 parser = argparse.ArgumentParser(
257 description='Provision Android devices with settings required for bots.') 284 description='Provision Android devices with settings required for bots.')
258 parser.add_argument('-d', '--device', metavar='SERIAL', 285 parser.add_argument('-d', '--device', metavar='SERIAL',
259 help='the serial number of the device to be provisioned' 286 help='the serial number of the device to be provisioned'
260 ' (the default is to provision all devices attached)') 287 ' (the default is to provision all devices attached)')
288 parser.add_argument('--phase', action='append', choices=_PHASES.ALL,
289 dest='phases',
290 help='Phases of provisioning to run. '
291 '(If omitted, all phases will be run.)')
261 parser.add_argument('--skip-wipe', action='store_true', default=False, 292 parser.add_argument('--skip-wipe', action='store_true', default=False,
262 help="don't wipe device data during provisioning") 293 help="don't wipe device data during provisioning")
263 parser.add_argument('--reboot-timeout', metavar='SECS', type=int, 294 parser.add_argument('--reboot-timeout', metavar='SECS', type=int,
264 help='when wiping the device, max number of seconds to' 295 help='when wiping the device, max number of seconds to'
265 ' wait after each reboot ' 296 ' wait after each reboot '
266 '(default: %s)' % _DEFAULT_TIMEOUTS.HELP_TEXT) 297 '(default: %s)' % _DEFAULT_TIMEOUTS.HELP_TEXT)
267 parser.add_argument('--min-battery-level', type=int, metavar='NUM', 298 parser.add_argument('--min-battery-level', type=int, metavar='NUM',
268 help='wait for the device to reach this minimum battery' 299 help='wait for the device to reach this minimum battery'
269 ' level before trying to continue') 300 ' level before trying to continue')
270 parser.add_argument('--disable-location', action='store_true', 301 parser.add_argument('--disable-location', action='store_true',
271 help='disable Google location services on devices') 302 help='disable Google location services on devices')
272 parser.add_argument('--disable-network', action='store_true', 303 parser.add_argument('--disable-network', action='store_true',
273 help='disable network access on devices') 304 help='disable network access on devices')
274 parser.add_argument('--disable-java-debug', action='store_false', 305 parser.add_argument('--disable-java-debug', action='store_false',
275 dest='enable_java_debug', default=True, 306 dest='enable_java_debug', default=True,
276 help='disable Java property asserts and JNI checking') 307 help='disable Java property asserts and JNI checking')
277 parser.add_argument('-t', '--target', default='Debug', 308 parser.add_argument('-t', '--target', default='Debug',
278 help='the build target (default: %(default)s)') 309 help='the build target (default: %(default)s)')
279 parser.add_argument('-r', '--auto-reconnect', action='store_true', 310 parser.add_argument('-r', '--auto-reconnect', action='store_true',
280 help='push binary which will reboot the device on adb' 311 help='push binary which will reboot the device on adb'
281 ' disconnections') 312 ' disconnections')
282 parser.add_argument('--adb-key-files', type=str, nargs='+', 313 parser.add_argument('--adb-key-files', type=str, nargs='+',
283 help='list of adb keys to push to device') 314 help='list of adb keys to push to device')
315 parser.add_argument('-v', '--verbose', action='count', default=1,
316 help='Log more information.')
284 args = parser.parse_args() 317 args = parser.parse_args()
285 constants.SetBuildType(args.target) 318 constants.SetBuildType(args.target)
286 319
320 run_tests_helper.SetLogLevel(args.verbose)
321
287 return ProvisionDevices(args) 322 return ProvisionDevices(args)
288 323
289 324
290 if __name__ == '__main__': 325 if __name__ == '__main__':
291 sys.exit(main()) 326 sys.exit(main())
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698