| OLD | NEW |
| 1 # Copyright 2015 The Swarming Authors. All rights reserved. | 1 # Copyright 2015 The Swarming Authors. All rights reserved. |
| 2 # Use of this source code is governed by the Apache v2.0 license that can be | 2 # Use of this source code is governed by the Apache v2.0 license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 """Android specific utility functions. | 5 """Android specific utility functions. |
| 6 | 6 |
| 7 This file serves as an API to bot_config.py. bot_config.py can be replaced on | 7 This file serves as an API to bot_config.py. bot_config.py can be replaced on |
| 8 the server to allow additional server-specific functionality. | 8 the server to allow additional server-specific functionality. |
| 9 """ | 9 """ |
| 10 | 10 |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 45 def __init__(self): | 45 def __init__(self): |
| 46 self._buf = '' | 46 self._buf = '' |
| 47 def update(self, msg): | 47 def update(self, msg): |
| 48 self._buf += msg | 48 self._buf += msg |
| 49 def digest(self): | 49 def digest(self): |
| 50 return self._buf | 50 return self._buf |
| 51 pkcs1.HASH_METHODS['SHA-1-PREHASHED'] = _Accum | 51 pkcs1.HASH_METHODS['SHA-1-PREHASHED'] = _Accum |
| 52 pkcs1.HASH_ASN1['SHA-1-PREHASHED'] = pkcs1.HASH_ASN1['SHA-1'] | 52 pkcs1.HASH_ASN1['SHA-1-PREHASHED'] = pkcs1.HASH_ASN1['SHA-1'] |
| 53 | 53 |
| 54 | 54 |
| 55 # Set when ADB is initialized. It contains one or multiple key used to | 55 # Both following are set when ADB is initialized. |
| 56 # authenticate to Android debug protocol (adb). | 56 # _ADB_KEYS is set to a list of _PythonRSASigner instances. It contains one or |
| 57 # multiple key used to authenticate to Android debug protocol (adb). |
| 57 _ADB_KEYS = None | 58 _ADB_KEYS = None |
| 59 # _ADB_KEYS_RAW is set to a dict(pub: priv) when initialized, with the raw |
| 60 # content of each key. |
| 61 _ADB_KEYS_RAW = None |
| 58 | 62 |
| 59 | 63 |
| 60 # Cache of /system/build.prop on Android devices connected to this host. | 64 # Cache of /system/build.prop on Android devices connected to this host. |
| 61 _BUILD_PROP_ANDROID = {} | 65 _BUILD_PROP_ANDROID = {} |
| 62 | 66 |
| 63 | 67 |
| 64 class _PythonRSASigner(object): | 68 class _PythonRSASigner(object): |
| 65 """Implements adb_protocol.AuthSigner using http://stuvel.eu/rsa.""" | 69 """Implements adb_protocol.AuthSigner using http://stuvel.eu/rsa.""" |
| 66 def __init__(self, pub, priv): | 70 def __init__(self, pub, priv): |
| 67 self.priv_key = _load_rsa_private_key(priv) | 71 self.priv_key = _load_rsa_private_key(priv) |
| (...skipping 110 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 178 logging.warning( | 182 logging.warning( |
| 179 'Trying unpluging and pluging it back: %s: %s', port_path, e) | 183 'Trying unpluging and pluging it back: %s: %s', port_path, e) |
| 180 # Always create a Device, even if it points to nothing. It makes using the | 184 # Always create a Device, even if it points to nothing. It makes using the |
| 181 # client code much easier. | 185 # client code much easier. |
| 182 return Device(cmd, port_path) | 186 return Device(cmd, port_path) |
| 183 | 187 |
| 184 | 188 |
| 185 ### Public API. | 189 ### Public API. |
| 186 | 190 |
| 187 | 191 |
| 192 # List of known CPU scaling governor values. |
| 193 KNOWN_CPU_SCALING_GOVERNOR_VALUES = ( |
| 194 'conservative', # Not available on Nexus 10 |
| 195 'interactive', # Default on Nexus 10. |
| 196 'ondemand', # Not available on Nexus 10. Default on Nexus 4 and later. |
| 197 'performance', |
| 198 'powersave', # Not available on Nexus 10. |
| 199 'userspace', |
| 200 ) |
| 201 |
| 202 |
| 188 class Device(object): | 203 class Device(object): |
| 189 """Wraps an AdbCommands to make it exception safe. | 204 """Wraps an AdbCommands to make it exception safe. |
| 190 | 205 |
| 191 The fact that exceptions can be thrown any time makes the client code really | 206 The fact that exceptions can be thrown any time makes the client code really |
| 192 hard to write safely. Convert USBError* to None return value. | 207 hard to write safely. Convert USBError* to None return value. |
| 193 """ | 208 """ |
| 194 def __init__(self, adb_cmd, port_path): | 209 def __init__(self, adb_cmd, port_path): |
| 195 if adb_cmd: | 210 if adb_cmd: |
| 196 assert isinstance(adb_cmd, adb.adb_commands.AdbCommands), adb_cmd | 211 assert isinstance(adb_cmd, adb.adb_commands.AdbCommands), adb_cmd |
| 197 self.adb_cmd = adb_cmd | 212 self.adb_cmd = adb_cmd |
| (...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 278 return '', int(parts[0]) | 293 return '', int(parts[0]) |
| 279 | 294 |
| 280 | 295 |
| 281 def initialize(pub_key, priv_key): | 296 def initialize(pub_key, priv_key): |
| 282 """Initialize Android support through adb. | 297 """Initialize Android support through adb. |
| 283 | 298 |
| 284 You can steal pub_key, priv_key pair from ~/.android/adbkey and | 299 You can steal pub_key, priv_key pair from ~/.android/adbkey and |
| 285 ~/.android/adbkey.pub. | 300 ~/.android/adbkey.pub. |
| 286 """ | 301 """ |
| 287 global _ADB_KEYS | 302 global _ADB_KEYS |
| 303 global _ADB_KEYS_RAW |
| 304 if _ADB_KEYS is not None: |
| 305 assert False, 'initialize() was called repeatedly: ignoring keys' |
| 288 assert bool(pub_key) == bool(priv_key) | 306 assert bool(pub_key) == bool(priv_key) |
| 289 if _ADB_KEYS is None: | 307 pub_key = pub_key.strip() if pub_key else pub_key |
| 290 _ADB_KEYS = [] | 308 priv_key = priv_key.strip() if priv_key else priv_key |
| 291 if pub_key: | |
| 292 try: | |
| 293 _ADB_KEYS.append(_PythonRSASigner(pub_key, priv_key)) | |
| 294 except ValueError as exc: | |
| 295 logging.warning('Ignoring adb private key: %s', exc) | |
| 296 | 309 |
| 297 # Try to add local adb keys if available. | 310 _ADB_KEYS = [] |
| 298 path = os.path.expanduser('~/.android/adbkey') | 311 _ADB_KEYS_RAW = {} |
| 299 if os.path.isfile(path) and os.path.isfile(path+'.pub'): | 312 if pub_key: |
| 300 with open(path + '.pub', 'rb') as f: | 313 _ADB_KEYS.append(_PythonRSASigner(pub_key, priv_key)) |
| 301 pub = f.read() | 314 _ADB_KEYS_RAW[pub_key] = priv_key |
| 302 with open(path, 'rb') as f: | |
| 303 priv = f.read() | |
| 304 try: | |
| 305 _ADB_KEYS.append(_PythonRSASigner(pub, priv)) | |
| 306 except ValueError as exc: | |
| 307 logging.warning('Ignoring adb private key %s: %s', path, exc) | |
| 308 | 315 |
| 309 if not _ADB_KEYS: | 316 # Try to add local adb keys if available. |
| 310 return False | 317 path = os.path.expanduser('~/.android/adbkey') |
| 311 else: | 318 if os.path.isfile(path) and os.path.isfile(path + '.pub'): |
| 312 if pub_key: | 319 with open(path + '.pub', 'rb') as f: |
| 313 logging.warning('initialize() was called repeatedly: ignoring keys') | 320 pub = f.read().strip() |
| 321 with open(path, 'rb') as f: |
| 322 priv = f.read().strip() |
| 323 _ADB_KEYS.append(_PythonRSASigner(pub, priv)) |
| 324 _ADB_KEYS_RAW[pub] = priv |
| 325 |
| 314 return bool(_ADB_KEYS) | 326 return bool(_ADB_KEYS) |
| 315 | 327 |
| 316 | 328 |
| 317 def kill_adb(): | 329 def kill_adb(): |
| 318 """Stops the adb daemon. | 330 """Stops the adb daemon. |
| 319 | 331 |
| 320 The Swarming bot doesn't use the Android's SDK adb. It uses python-adb to | 332 The Swarming bot doesn't use the Android's SDK adb. It uses python-adb to |
| 321 directly communicate with the devices. This works much better when a lot of | 333 directly communicate with the devices. This works much better when a lot of |
| 322 devices are connected to the host. | 334 devices are connected to the host. |
| 323 | 335 |
| (...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 383 _BUILD_PROP_ANDROID.pop(key) | 395 _BUILD_PROP_ANDROID.pop(key) |
| 384 return devices | 396 return devices |
| 385 | 397 |
| 386 | 398 |
| 387 def close_devices(devices): | 399 def close_devices(devices): |
| 388 """Closes all devices opened by get_devices().""" | 400 """Closes all devices opened by get_devices().""" |
| 389 for _, device in sorted((devices or {}).iteritems()): | 401 for _, device in sorted((devices or {}).iteritems()): |
| 390 device.close() | 402 device.close() |
| 391 | 403 |
| 392 | 404 |
| 393 class CpuScalingGovernor(object): | |
| 394 """List of valid CPU scaling governor values.""" | |
| 395 ON_DEMAND = 'ondemand' | |
| 396 PERFORMANCE = 'performance' | |
| 397 CONSERVATIVE = 'conservative' | |
| 398 POWER_SAVE = 'powersave' | |
| 399 USER_DEFINED = 'userspace' | |
| 400 | |
| 401 @classmethod | |
| 402 def is_valid(cls, name): | |
| 403 return name in [getattr(cls, m) for m in dir(cls) if m[0].isupper()] | |
| 404 | |
| 405 | |
| 406 def set_cpu_scaling_governor(device, governor): | 405 def set_cpu_scaling_governor(device, governor): |
| 407 """Sets the CPU scaling governor to the one specified. | 406 """Sets the CPU scaling governor to the one specified. |
| 408 | 407 |
| 409 Returns: | 408 Returns: |
| 410 True on success. | 409 True on success. |
| 411 """ | 410 """ |
| 412 assert isinstance(device, Device), device | 411 assert isinstance(device, Device), device |
| 413 assert CpuScalingGovernor.is_valid(governor), governor | 412 assert governor in KNOWN_CPU_SCALING_GOVERNOR_VALUES, governor |
| 414 _, exit_code = device.shell( | 413 _, exit_code = device.shell( |
| 415 'echo "%s">/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor' % | 414 'echo "%s">/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor' % |
| 416 governor) | 415 governor) |
| 417 return exit_code == 0 | 416 return exit_code == 0 |
| 418 | 417 |
| 419 | 418 |
| 420 def set_cpu_scaling(device, speed): | 419 def set_cpu_scaling(device, speed): |
| 421 """Enforces strict CPU speed and disable the CPU scaling governor. | 420 """Enforces strict CPU speed and disable the CPU scaling governor. |
| 422 | 421 |
| 423 Returns: | 422 Returns: |
| 424 True on success. | 423 True on success. |
| 425 """ | 424 """ |
| 426 assert isinstance(device, Device), device | 425 assert isinstance(device, Device), device |
| 427 assert isinstance(speed, int), speed | 426 assert isinstance(speed, int), speed |
| 428 assert 10000 <= speed <= 10000000, speed | 427 assert 10000 <= speed <= 10000000, speed |
| 429 success = set_cpu_scaling_governor(device, CpuScalingGovernor.USER_DEFINED) | 428 success = set_cpu_scaling_governor(device, 'userspace') |
| 430 _, exit_code = device.shell( | 429 _, exit_code = device.shell( |
| 431 'echo "%d">/sys/devices/system/cpu/cpu0/cpufreq/scaling_setspeed' % | 430 'echo "%d">/sys/devices/system/cpu/cpu0/cpufreq/scaling_setspeed' % |
| 432 speed) | 431 speed) |
| 433 return success and exit_code == 0 | 432 return success and exit_code == 0 |
| 434 | 433 |
| 435 | 434 |
| 436 def get_build_prop(device): | 435 def get_build_prop(device): |
| 437 """Returns the system properties for a device. | 436 """Returns the system properties for a device. |
| 438 | 437 |
| 439 This isn't really changing through the lifetime of a bot. One corner case is | 438 This isn't really changing through the lifetime of a bot. One corner case is |
| (...skipping 131 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 571 | 570 |
| 572 def get_ip(device): | 571 def get_ip(device): |
| 573 """Returns the IP address.""" | 572 """Returns the IP address.""" |
| 574 assert isinstance(device, Device), device | 573 assert isinstance(device, Device), device |
| 575 # If ever needed, read wifi.interface from /system/build.prop if a device | 574 # If ever needed, read wifi.interface from /system/build.prop if a device |
| 576 # doesn't use wlan0. | 575 # doesn't use wlan0. |
| 577 ip, _ = device.shell('getprop dhcp.wlan0.ipaddress') | 576 ip, _ = device.shell('getprop dhcp.wlan0.ipaddress') |
| 578 if not ip: | 577 if not ip: |
| 579 return None | 578 return None |
| 580 return ip.strip() | 579 return ip.strip() |
| OLD | NEW |