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