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