|
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 |
|
3 # found in the LICENSE file. |
|
4 |
|
5 import logging |
|
6 import os |
|
7 import re |
|
8 import sys |
|
9 import time |
|
10 |
|
11 import android_commands |
|
12 import cmd_helper |
|
13 import constants |
|
14 import ports |
|
15 |
|
16 from pylib import pexpect |
|
17 |
|
18 class Forwarder(object): |
|
19 """Class to manage port forwards from the device to the host.""" |
|
20 |
|
21 _DEVICE_FORWARDER_PATH = constants.TEST_EXECUTABLE_DIR + '/device_forwarder' |
|
22 |
|
23 # Unix Abstract socket path: |
|
24 _DEVICE_ADB_CONTROL_PORT = 'chrome_device_forwarder' |
|
25 _TIMEOUT_SECS = 30 |
|
26 |
|
27 def __init__(self, adb, port_pairs, tool, host_name, build_type): |
|
28 """Forwards TCP ports on the device back to the host. |
|
29 |
|
30 Works like adb forward, but in reverse. |
|
31 |
|
32 Args: |
|
33 adb: Instance of AndroidCommands for talking to the device. |
|
34 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 |
|
36 port will by dynamically assigned on the device. You can |
|
37 get the number of the assigned port using the |
|
38 DevicePortForHostPort method. |
|
39 tool: Tool class to use to get wrapper, if necessary, for executing the |
|
40 forwarder (see valgrind_tools.py). |
|
41 host_name: Address to forward to, must be addressable from the |
|
42 host machine. Usually use loopback '127.0.0.1'. |
|
43 build_type: 'Release' or 'Debug'. |
|
44 |
|
45 Raises: |
|
46 Exception on failure to forward the port. |
|
47 """ |
|
48 self._adb = adb |
|
49 self._host_to_device_port_map = dict() |
|
50 self._host_process = None |
|
51 self._device_process = None |
|
52 self._adb_forward_process = None |
|
53 |
|
54 self._host_adb_control_port = ports.AllocateTestServerPort() |
|
55 if not self._host_adb_control_port: |
|
56 raise Exception('Failed to allocate a TCP port in the host machine.') |
|
57 adb.PushIfNeeded( |
|
58 os.path.join(constants.CHROME_DIR, 'out', build_type, |
|
59 'device_forwarder'), |
|
60 Forwarder._DEVICE_FORWARDER_PATH) |
|
61 self._host_forwarder_path = os.path.join(constants.CHROME_DIR, |
|
62 'out', |
|
63 build_type, |
|
64 'host_forwarder') |
|
65 forward_string = ['%d:%d:%s' % |
|
66 (device, host, host_name) for device, host in port_pairs] |
|
67 logging.info('Forwarding ports: %s', forward_string) |
|
68 timeout_sec = 5 |
|
69 host_pattern = 'host_forwarder.*' + ' '.join(forward_string) |
|
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: |
|
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 |
|
192 def DevicePortForHostPort(self, host_port): |
|
193 """Get the device port that corresponds to a given host port.""" |
|
194 return self._host_to_device_port_map.get(host_port) |
|
195 |
|
196 def Close(self): |
|
197 """Terminate the forwarder process.""" |
|
198 self._CloseProcess() |