| OLD | NEW |
| 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 import logging | 5 import logging |
| 6 import os | 6 import os |
| 7 import re | 7 import re |
| 8 import sys | 8 import sys |
| 9 import time | 9 import time |
| 10 | 10 |
| 11 import android_commands | 11 import android_commands |
| 12 import cmd_helper | 12 import cmd_helper |
| 13 import constants | 13 import constants |
| 14 import ports | 14 import ports |
| 15 | 15 |
| 16 from pylib import pexpect | 16 from pylib import pexpect |
| 17 | 17 |
| 18 |
| 19 def _MakeBinaryPath(build_type, binary_name): |
| 20 return os.path.join(constants.CHROME_DIR, 'out', build_type, binary_name) |
| 21 |
| 22 |
| 18 class Forwarder(object): | 23 class Forwarder(object): |
| 19 """Class to manage port forwards from the device to the host.""" | 24 """Class to manage port forwards from the device to the host.""" |
| 20 | 25 |
| 21 _DEVICE_FORWARDER_PATH = constants.TEST_EXECUTABLE_DIR + '/device_forwarder' | 26 _DEVICE_FORWARDER_PATH = constants.TEST_EXECUTABLE_DIR + '/device_forwarder' |
| 22 | 27 |
| 23 # Unix Abstract socket path: | 28 # Unix Abstract socket path: |
| 24 _DEVICE_ADB_CONTROL_PORT = 'chrome_device_forwarder' | 29 _DEVICE_ADB_CONTROL_PORT = 'chrome_device_forwarder' |
| 25 _TIMEOUT_SECS = 30 | 30 _TIMEOUT_SECS = 30 |
| 26 | 31 |
| 27 def __init__(self, adb, port_pairs, tool, host_name, build_type): | 32 def __init__(self, adb, build_type): |
| 28 """Forwards TCP ports on the device back to the host. | 33 """Forwards TCP ports on the device back to the host. |
| 29 | 34 |
| 30 Works like adb forward, but in reverse. | 35 Works like adb forward, but in reverse. |
| 31 | 36 |
| 32 Args: | 37 Args: |
| 33 adb: Instance of AndroidCommands for talking to the device. | 38 adb: Instance of AndroidCommands for talking to the device. |
| 39 build_type: 'Release' or 'Debug'. |
| 40 """ |
| 41 assert build_type in ('Release', 'Debug') |
| 42 self._adb = adb |
| 43 self._host_to_device_port_map = dict() |
| 44 self._device_process = None |
| 45 self._host_forwarder_path = _MakeBinaryPath(build_type, 'host_forwarder') |
| 46 self._device_forwarder_path = _MakeBinaryPath( |
| 47 build_type, 'device_forwarder') |
| 48 |
| 49 def Run(self, port_pairs, tool, host_name): |
| 50 """Runs the forwarder. |
| 51 |
| 52 Args: |
| 34 port_pairs: A list of tuples (device_port, host_port) to forward. Note | 53 port_pairs: A list of tuples (device_port, host_port) to forward. Note |
| 35 that you can specify 0 as a device_port, in which case a | 54 that you can specify 0 as a device_port, in which case a |
| 36 port will by dynamically assigned on the device. You can | 55 port will by dynamically assigned on the device. You can |
| 37 get the number of the assigned port using the | 56 get the number of the assigned port using the |
| 38 DevicePortForHostPort method. | 57 DevicePortForHostPort method. |
| 39 tool: Tool class to use to get wrapper, if necessary, for executing the | 58 tool: Tool class to use to get wrapper, if necessary, for executing the |
| 40 forwarder (see valgrind_tools.py). | 59 forwarder (see valgrind_tools.py). |
| 41 host_name: Address to forward to, must be addressable from the | 60 host_name: Address to forward to, must be addressable from the |
| 42 host machine. Usually use loopback '127.0.0.1'. | 61 host machine. Usually use loopback '127.0.0.1'. |
| 43 build_type: 'Release' or 'Debug'. | |
| 44 | 62 |
| 45 Raises: | 63 Raises: |
| 46 Exception on failure to forward the port. | 64 Exception on failure to forward the port. |
| 47 """ | 65 """ |
| 48 self._adb = adb | 66 host_adb_control_port = ports.AllocateTestServerPort() |
| 49 self._host_to_device_port_map = dict() | 67 if not host_adb_control_port: |
| 50 self._host_process = None | 68 raise Exception('Failed to allocate a TCP port in the host machine.') |
| 51 self._device_process = None | 69 self._adb.PushIfNeeded(self._device_forwarder_path, |
| 52 self._adb_forward_process = None | 70 Forwarder._DEVICE_FORWARDER_PATH) |
| 71 redirection_commands = [ |
| 72 '%d:%d:%d:%s' % (host_adb_control_port, device, host, |
| 73 host_name) for device, host in port_pairs] |
| 74 logging.info('Command format: <ADB port>:<Device port>' + |
| 75 '[:<Forward to port>:<Forward to address>]') |
| 76 logging.info('Forwarding using commands: %s', redirection_commands) |
| 77 if cmd_helper.RunCmd( |
| 78 ['adb', '-s', self._adb._adb.GetSerialNumber(), 'forward', |
| 79 'tcp:%s' % host_adb_control_port, |
| 80 'localabstract:%s' % Forwarder._DEVICE_ADB_CONTROL_PORT]) != 0: |
| 81 raise Exception('Error while running adb forward.') |
| 53 | 82 |
| 54 self._host_adb_control_port = ports.AllocateTestServerPort() | 83 if not self._adb.ExtractPid('device_forwarder'): |
| 55 if not self._host_adb_control_port: | 84 # TODO(pliard): Get rid of pexpect here and make device_forwarder a daemon |
| 56 raise Exception('Failed to allocate a TCP port in the host machine.') | 85 # with a blocking CLI process that exits with a proper exit code and not |
| 57 adb.PushIfNeeded( | 86 # while the daemon is still setting up. This would be similar to how |
| 58 os.path.join(constants.CHROME_DIR, 'out', build_type, | 87 # host_forwarder works. |
| 59 'device_forwarder'), | 88 self._device_process = pexpect.spawn( |
| 60 Forwarder._DEVICE_FORWARDER_PATH) | 89 'adb', ['-s', |
| 61 self._host_forwarder_path = os.path.join(constants.CHROME_DIR, | 90 self._adb._adb.GetSerialNumber(), |
| 62 'out', | 91 'shell', |
| 63 build_type, | 92 '%s %s -D --adb_sock=%s' % ( |
| 64 'host_forwarder') | 93 tool.GetUtilWrapper(), |
| 65 forward_string = ['%d:%d:%s' % | 94 Forwarder._DEVICE_FORWARDER_PATH, |
| 66 (device, host, host_name) for device, host in port_pairs] | 95 Forwarder._DEVICE_ADB_CONTROL_PORT)]) |
| 67 logging.info('Forwarding ports: %s', forward_string) | 96 device_success_re = re.compile('Starting Device Forwarder.') |
| 97 device_failure_re = re.compile('.*:ERROR:(.*)') |
| 98 index = self._device_process.expect([device_success_re, |
| 99 device_failure_re, |
| 100 pexpect.EOF, |
| 101 pexpect.TIMEOUT], |
| 102 Forwarder._TIMEOUT_SECS) |
| 103 if index == 1: |
| 104 error_msg = str(self._device_process.match.group(1)) |
| 105 logging.error(self._device_process.before) |
| 106 self._device_process.close() |
| 107 raise Exception('Failed to start Device Forwarder with Error: %s' % |
| 108 error_msg) |
| 109 elif index == 2: |
| 110 logging.error(self._device_process.before) |
| 111 self._device_process.close() |
| 112 raise Exception( |
| 113 'Unexpected EOF while trying to start Device Forwarder.') |
| 114 elif index == 3: |
| 115 logging.error(self._device_process.before) |
| 116 self._device_process.close() |
| 117 raise Exception('Timeout while trying start Device Forwarder') |
| 118 |
| 119 for redirection_command in redirection_commands: |
| 120 (exit_code, output) = cmd_helper.GetCmdStatusAndOutput( |
| 121 [self._host_forwarder_path, redirection_command]) |
| 122 if exit_code != 0: |
| 123 raise Exception('%s exited with %d: %s' % (self._host_forwarder_path, |
| 124 exit_code, output)) |
| 125 tokens = output.split(':') |
| 126 if len(tokens) != 2: |
| 127 raise Exception('Unexpected host forwarder output "%s", ' + |
| 128 'expected "device_port:host_port"' % output) |
| 129 device_port = int(tokens[0]) |
| 130 host_port = int(tokens[1]) |
| 131 self._host_to_device_port_map[host_port] = device_port |
| 132 logging.info('Forwarding device port: %d to host port: %d.', device_port, |
| 133 host_port) |
| 134 |
| 135 @staticmethod |
| 136 def KillHost(build_type): |
| 137 host_forwarder_path = _MakeBinaryPath(build_type, 'host_forwarder') |
| 138 (exit_code, output) = cmd_helper.GetCmdStatusAndOutput( |
| 139 [host_forwarder_path, 'kill-server']) |
| 140 if exit_code != 0: |
| 141 raise Exception('%s exited with %d: %s' % (host_forwarder_path, |
| 142 exit_code, output)) |
| 143 |
| 144 @staticmethod |
| 145 def KillDevice(adb): |
| 146 logging.info('Killing device_forwarder.') |
| 68 timeout_sec = 5 | 147 timeout_sec = 5 |
| 69 host_pattern = 'host_forwarder.*' + ' '.join(forward_string) | 148 processes_killed = adb.KillAllBlocking('device_forwarder', timeout_sec) |
| 70 # TODO(felipeg): Rather than using a blocking kill() here, the device | |
| 71 # forwarder could try to bind the Unix Domain Socket until it succeeds or | |
| 72 # while it fails because the socket is already bound (with appropriate | |
| 73 # timeout handling obviously). | |
| 74 self._KillHostForwarderBlocking(host_pattern, timeout_sec) | |
| 75 self._KillDeviceForwarderBlocking(timeout_sec) | |
| 76 self._adb_forward_process = pexpect.spawn( | |
| 77 'adb', ['-s', | |
| 78 adb._adb.GetSerialNumber(), | |
| 79 'forward', | |
| 80 'tcp:%s' % self._host_adb_control_port, | |
| 81 'localabstract:%s' % Forwarder._DEVICE_ADB_CONTROL_PORT]) | |
| 82 self._device_process = pexpect.spawn( | |
| 83 'adb', ['-s', | |
| 84 adb._adb.GetSerialNumber(), | |
| 85 'shell', | |
| 86 '%s %s -D --adb_sock=%s' % ( | |
| 87 tool.GetUtilWrapper(), | |
| 88 Forwarder._DEVICE_FORWARDER_PATH, | |
| 89 Forwarder._DEVICE_ADB_CONTROL_PORT)]) | |
| 90 | |
| 91 device_success_re = re.compile('Starting Device Forwarder.') | |
| 92 device_failure_re = re.compile('.*:ERROR:(.*)') | |
| 93 index = self._device_process.expect([device_success_re, | |
| 94 device_failure_re, | |
| 95 pexpect.EOF, | |
| 96 pexpect.TIMEOUT], | |
| 97 Forwarder._TIMEOUT_SECS) | |
| 98 if index == 1: | |
| 99 # Failure | |
| 100 error_msg = str(self._device_process.match.group(1)) | |
| 101 logging.error(self._device_process.before) | |
| 102 self._CloseProcess() | |
| 103 raise Exception('Failed to start Device Forwarder with Error: %s' % | |
| 104 error_msg) | |
| 105 elif index == 2: | |
| 106 logging.error(self._device_process.before) | |
| 107 self._CloseProcess() | |
| 108 raise Exception('Unexpected EOF while trying to start Device Forwarder.') | |
| 109 elif index == 3: | |
| 110 logging.error(self._device_process.before) | |
| 111 self._CloseProcess() | |
| 112 raise Exception('Timeout while trying start Device Forwarder') | |
| 113 | |
| 114 self._host_process = pexpect.spawn(self._host_forwarder_path, | |
| 115 ['--adb_port=%s' % ( | |
| 116 self._host_adb_control_port)] + | |
| 117 forward_string) | |
| 118 | |
| 119 # Read the output of the command to determine which device ports where | |
| 120 # forwarded to which host ports (necessary if | |
| 121 host_success_re = re.compile('Forwarding device port (\d+) to host (\d+):') | |
| 122 host_failure_re = re.compile('Couldn\'t start forwarder server for port ' | |
| 123 'spec: (\d+):(\d+)') | |
| 124 for pair in port_pairs: | |
| 125 index = self._host_process.expect([host_success_re, | |
| 126 host_failure_re, | |
| 127 pexpect.EOF, | |
| 128 pexpect.TIMEOUT], | |
| 129 Forwarder._TIMEOUT_SECS) | |
| 130 if index == 0: | |
| 131 # Success | |
| 132 device_port = int(self._host_process.match.group(1)) | |
| 133 host_port = int(self._host_process.match.group(2)) | |
| 134 self._host_to_device_port_map[host_port] = device_port | |
| 135 logging.info("Forwarding device port: %d to host port: %d." % | |
| 136 (device_port, host_port)) | |
| 137 elif index == 1: | |
| 138 # Failure | |
| 139 device_port = int(self._host_process.match.group(1)) | |
| 140 host_port = int(self._host_process.match.group(2)) | |
| 141 self._CloseProcess() | |
| 142 raise Exception('Failed to forward port %d to %d' % (device_port, | |
| 143 host_port)) | |
| 144 elif index == 2: | |
| 145 logging.error(self._host_process.before) | |
| 146 self._CloseProcess() | |
| 147 raise Exception('Unexpected EOF while trying to forward ports %s' % | |
| 148 port_pairs) | |
| 149 elif index == 3: | |
| 150 logging.error(self._host_process.before) | |
| 151 self._CloseProcess() | |
| 152 raise Exception('Timeout while trying to forward ports %s' % port_pairs) | |
| 153 | |
| 154 def _KillHostForwarderBlocking(self, host_pattern, timeout_sec): | |
| 155 """Kills any existing host forwarders using the provided pattern. | |
| 156 | |
| 157 Note that this waits until the process terminates. | |
| 158 """ | |
| 159 cmd_helper.RunCmd(['pkill', '-f', host_pattern]) | |
| 160 elapsed = 0 | |
| 161 wait_period = 0.1 | |
| 162 while not cmd_helper.RunCmd(['pgrep', '-f', host_pattern]) and ( | |
| 163 elapsed < timeout_sec): | |
| 164 time.sleep(wait_period) | |
| 165 elapsed += wait_period | |
| 166 if elapsed >= timeout_sec: | |
| 167 raise Exception('Timed out while killing ' + host_pattern) | |
| 168 | |
| 169 def _KillDeviceForwarderBlocking(self, timeout_sec): | |
| 170 """Kills any existing device forwarders. | |
| 171 | |
| 172 Note that this waits until the process terminates. | |
| 173 """ | |
| 174 processes_killed = self._adb.KillAllBlocking( | |
| 175 'device_forwarder', timeout_sec) | |
| 176 if not processes_killed: | 149 if not processes_killed: |
| 177 pids = self._adb.ExtractPid('device_forwarder') | 150 pids = adb.ExtractPid('device_forwarder') |
| 178 if pids: | 151 if pids: |
| 179 raise Exception('Timed out while killing device_forwarder') | 152 raise Exception('Timed out while killing device_forwarder') |
| 180 | 153 |
| 181 def _CloseProcess(self): | |
| 182 if self._host_process: | |
| 183 self._host_process.close() | |
| 184 if self._device_process: | |
| 185 self._device_process.close() | |
| 186 if self._adb_forward_process: | |
| 187 self._adb_forward_process.close() | |
| 188 self._host_process = None | |
| 189 self._device_process = None | |
| 190 self._adb_forward_process = None | |
| 191 | |
| 192 def DevicePortForHostPort(self, host_port): | 154 def DevicePortForHostPort(self, host_port): |
| 193 """Get the device port that corresponds to a given host port.""" | 155 """Get the device port that corresponds to a given host port.""" |
| 194 return self._host_to_device_port_map.get(host_port) | 156 return self._host_to_device_port_map.get(host_port) |
| 195 | 157 |
| 196 def Close(self): | 158 def Close(self): |
| 197 """Terminate the forwarder process.""" | 159 """Terminate the forwarder process.""" |
| 198 self._CloseProcess() | 160 if self._device_process: |
| 161 self._device_process.close() |
| 162 self._device_process = None |
| OLD | NEW |