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