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 threading |
9 import time | 10 import time |
10 | 11 |
11 import android_commands | 12 import android_commands |
12 import cmd_helper | 13 import cmd_helper |
13 import constants | 14 import constants |
14 import ports | 15 import ports |
15 | 16 |
16 from pylib import pexpect | 17 from pylib import pexpect |
17 | 18 |
18 | 19 |
19 def _MakeBinaryPath(build_type, binary_name): | 20 def _MakeBinaryPath(build_type, binary_name): |
20 return os.path.join(cmd_helper.OutDirectory.get(), build_type, binary_name) | 21 return os.path.join(cmd_helper.OutDirectory.get(), build_type, binary_name) |
21 | 22 |
22 | 23 |
23 class Forwarder(object): | 24 class Forwarder(object): |
24 """Class to manage port forwards from the device to the host.""" | 25 """Thread-safe class to manage port forwards from the device to the host.""" |
25 | 26 |
26 # Unix Abstract socket path: | 27 # Unix Abstract socket path: |
27 _DEVICE_ADB_CONTROL_PORT = 'chrome_device_forwarder' | 28 _DEVICE_ADB_CONTROL_PORT = 'chrome_device_forwarder' |
28 _TIMEOUT_SECS = 30 | 29 _TIMEOUT_SECS = 30 |
29 | 30 |
30 _DEVICE_FORWARDER_FOLDER = (constants.TEST_EXECUTABLE_DIR + | 31 _DEVICE_FORWARDER_FOLDER = (constants.TEST_EXECUTABLE_DIR + |
31 '/forwarder/') | 32 '/forwarder/') |
32 _DEVICE_FORWARDER_PATH = (constants.TEST_EXECUTABLE_DIR + | 33 _DEVICE_FORWARDER_PATH = (constants.TEST_EXECUTABLE_DIR + |
33 '/forwarder/device_forwarder') | 34 '/forwarder/device_forwarder') |
34 _LD_LIBRARY_PATH = 'LD_LIBRARY_PATH=%s' % _DEVICE_FORWARDER_FOLDER | 35 _LD_LIBRARY_PATH = 'LD_LIBRARY_PATH=%s' % _DEVICE_FORWARDER_FOLDER |
35 | 36 |
36 def __init__(self, adb, build_type): | 37 def __init__(self, adb, build_type): |
37 """Forwards TCP ports on the device back to the host. | 38 """Forwards TCP ports on the device back to the host. |
38 | 39 |
39 Works like adb forward, but in reverse. | 40 Works like adb forward, but in reverse. |
40 | 41 |
41 Args: | 42 Args: |
42 adb: Instance of AndroidCommands for talking to the device. | 43 adb: Instance of AndroidCommands for talking to the device. |
43 build_type: 'Release' or 'Debug'. | 44 build_type: 'Release' or 'Debug'. |
44 """ | 45 """ |
45 assert build_type in ('Release', 'Debug') | 46 assert build_type in ('Release', 'Debug') |
46 self._adb = adb | 47 self._adb = adb |
47 self._host_to_device_port_map = dict() | 48 self._host_to_device_port_map = dict() |
48 self._device_process = None | 49 self._device_initialized = False |
| 50 self._host_adb_control_port = 0 |
| 51 self._lock = threading.Lock() |
49 self._host_forwarder_path = _MakeBinaryPath(build_type, 'host_forwarder') | 52 self._host_forwarder_path = _MakeBinaryPath(build_type, 'host_forwarder') |
50 self._device_forwarder_path_on_host = os.path.join( | 53 self._device_forwarder_path_on_host = os.path.join( |
51 cmd_helper.OutDirectory.get(), build_type, 'forwarder_dist') | 54 cmd_helper.OutDirectory.get(), build_type, 'forwarder_dist') |
52 | 55 |
53 def Run(self, port_pairs, tool, host_name): | 56 def Run(self, port_pairs, tool, host_name): |
54 """Runs the forwarder. | 57 """Runs the forwarder. |
55 | 58 |
56 Args: | 59 Args: |
57 port_pairs: A list of tuples (device_port, host_port) to forward. Note | 60 port_pairs: A list of tuples (device_port, host_port) to forward. Note |
58 that you can specify 0 as a device_port, in which case a | 61 that you can specify 0 as a device_port, in which case a |
59 port will by dynamically assigned on the device. You can | 62 port will by dynamically assigned on the device. You can |
60 get the number of the assigned port using the | 63 get the number of the assigned port using the |
61 DevicePortForHostPort method. | 64 DevicePortForHostPort method. |
62 tool: Tool class to use to get wrapper, if necessary, for executing the | 65 tool: Tool class to use to get wrapper, if necessary, for executing the |
63 forwarder (see valgrind_tools.py). | 66 forwarder (see valgrind_tools.py). |
64 host_name: Address to forward to, must be addressable from the | 67 host_name: Address to forward to, must be addressable from the |
65 host machine. Usually use loopback '127.0.0.1'. | 68 host machine. Usually use loopback '127.0.0.1'. |
66 | 69 |
67 Raises: | 70 Raises: |
68 Exception on failure to forward the port. | 71 Exception on failure to forward the port. |
69 """ | 72 """ |
| 73 with self._lock: |
| 74 self._InitDeviceLocked(tool) |
| 75 self._InitHostLocked() |
| 76 redirection_commands = [ |
| 77 '%d:%d:%d:%s' % (self._host_adb_control_port, device, host, |
| 78 host_name) for device, host in port_pairs] |
| 79 logging.info('Command format: <ADB port>:<Device port>' + |
| 80 '[:<Forward to port>:<Forward to address>]') |
| 81 logging.info('Forwarding using commands: %s', redirection_commands) |
| 82 |
| 83 for redirection_command in redirection_commands: |
| 84 try: |
| 85 (exit_code, output) = cmd_helper.GetCmdStatusAndOutput( |
| 86 [self._host_forwarder_path, redirection_command]) |
| 87 except OSError as e: |
| 88 if e.errno == 2: |
| 89 raise Exception('Unable to start host forwarder. Make sure you have' |
| 90 ' built host_forwarder.') |
| 91 else: raise |
| 92 if exit_code != 0: |
| 93 raise Exception('%s exited with %d:\n%s' % ( |
| 94 self._host_forwarder_path, exit_code, '\n'.join(output))) |
| 95 tokens = output.split(':') |
| 96 if len(tokens) != 2: |
| 97 raise Exception('Unexpected host forwarder output "%s", ' + |
| 98 'expected "device_port:host_port"' % output) |
| 99 device_port = int(tokens[0]) |
| 100 host_port = int(tokens[1]) |
| 101 self._host_to_device_port_map[host_port] = device_port |
| 102 logging.info('Forwarding device port: %d to host port: %d.', |
| 103 device_port, host_port) |
| 104 |
| 105 def _InitHostLocked(self): |
| 106 """Initializes the host forwarder process (only once).""" |
| 107 if self._host_adb_control_port: |
| 108 return |
70 self._host_adb_control_port = ports.AllocateTestServerPort() | 109 self._host_adb_control_port = ports.AllocateTestServerPort() |
71 if not self._host_adb_control_port: | 110 if not self._host_adb_control_port: |
72 raise Exception('Failed to allocate a TCP port in the host machine.') | 111 raise Exception('Failed to allocate a TCP port in the host machine.') |
73 self._adb.PushIfNeeded( | |
74 self._device_forwarder_path_on_host, Forwarder._DEVICE_FORWARDER_FOLDER) | |
75 redirection_commands = [ | |
76 '%d:%d:%d:%s' % (self._host_adb_control_port, device, host, | |
77 host_name) for device, host in port_pairs] | |
78 logging.info('Command format: <ADB port>:<Device port>' + | |
79 '[:<Forward to port>:<Forward to address>]') | |
80 logging.info('Forwarding using commands: %s', redirection_commands) | |
81 if cmd_helper.RunCmd( | 112 if cmd_helper.RunCmd( |
82 ['adb', '-s', self._adb._adb.GetSerialNumber(), 'forward', | 113 ['adb', '-s', self._adb._adb.GetSerialNumber(), 'forward', |
83 'tcp:%s' % self._host_adb_control_port, | 114 'tcp:%s' % self._host_adb_control_port, |
84 'localabstract:%s' % Forwarder._DEVICE_ADB_CONTROL_PORT]) != 0: | 115 'localabstract:%s' % Forwarder._DEVICE_ADB_CONTROL_PORT]) != 0: |
85 raise Exception('Error while running adb forward.') | 116 raise Exception('Error while running adb forward.') |
86 | 117 |
| 118 def _InitDeviceLocked(self, tool): |
| 119 """Initializes the device forwarder process (only once).""" |
| 120 if self._device_initialized: |
| 121 return |
| 122 self._adb.PushIfNeeded( |
| 123 self._device_forwarder_path_on_host, |
| 124 Forwarder._DEVICE_FORWARDER_FOLDER) |
87 (exit_code, output) = self._adb.GetShellCommandStatusAndOutput( | 125 (exit_code, output) = self._adb.GetShellCommandStatusAndOutput( |
88 '%s %s %s %s' % (Forwarder._LD_LIBRARY_PATH, tool.GetUtilWrapper(), | 126 '%s %s %s %s' % (Forwarder._LD_LIBRARY_PATH, tool.GetUtilWrapper(), |
89 Forwarder._DEVICE_FORWARDER_PATH, | 127 Forwarder._DEVICE_FORWARDER_PATH, |
90 Forwarder._DEVICE_ADB_CONTROL_PORT)) | 128 Forwarder._DEVICE_ADB_CONTROL_PORT)) |
91 if exit_code != 0: | 129 if exit_code != 0: |
92 raise Exception( | 130 raise Exception( |
93 'Failed to start device forwarder:\n%s' % '\n'.join(output)) | 131 'Failed to start device forwarder:\n%s' % '\n'.join(output)) |
| 132 self._device_initialized = True |
94 | 133 |
95 for redirection_command in redirection_commands: | 134 def UnmapDevicePort(self, device_port): |
96 try: | 135 """Unmaps a previously forwarded device port. |
97 (exit_code, output) = cmd_helper.GetCmdStatusAndOutput( | 136 |
98 [self._host_forwarder_path, redirection_command]) | 137 Args: |
99 except OSError as e: | 138 device_port: A previously forwarded port (through Run()). |
100 if e.errno == 2: | 139 """ |
101 raise Exception('Unable to start host forwarder. Make sure you have ' | 140 with self._lock: |
102 'built host_forwarder.') | 141 # Please note the minus sign below. |
103 else: raise | 142 redirection_command = '%d:-%d' % ( |
| 143 self._host_adb_control_port, device_port) |
| 144 (exit_code, output) = cmd_helper.GetCmdStatusAndOutput( |
| 145 [self._host_forwarder_path, redirection_command]) |
104 if exit_code != 0: | 146 if exit_code != 0: |
105 raise Exception('%s exited with %d:\n%s' % ( | 147 raise Exception('%s exited with %d:\n%s' % ( |
106 self._host_forwarder_path, exit_code, '\n'.join(output))) | 148 self._host_forwarder_path, exit_code, '\n'.join(output))) |
107 tokens = output.split(':') | |
108 if len(tokens) != 2: | |
109 raise Exception('Unexpected host forwarder output "%s", ' + | |
110 'expected "device_port:host_port"' % output) | |
111 device_port = int(tokens[0]) | |
112 host_port = int(tokens[1]) | |
113 self._host_to_device_port_map[host_port] = device_port | |
114 logging.info('Forwarding device port: %d to host port: %d.', device_port, | |
115 host_port) | |
116 | |
117 def UnmapDevicePort(self, device_port): | |
118 # Please note the minus sign below. | |
119 redirection_command = '%d:-%d' % (self._host_adb_control_port, device_port) | |
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:\n%s' % ( | |
124 self._host_forwarder_path, exit_code, '\n'.join(output))) | |
125 | 149 |
126 @staticmethod | 150 @staticmethod |
127 def KillHost(build_type): | 151 def KillHost(build_type): |
| 152 """Kills the forwarder process running on the host. |
| 153 |
| 154 Args: |
| 155 build_type: 'Release' or 'Debug' |
| 156 """ |
128 logging.info('Killing host_forwarder.') | 157 logging.info('Killing host_forwarder.') |
129 host_forwarder_path = _MakeBinaryPath(build_type, 'host_forwarder') | 158 host_forwarder_path = _MakeBinaryPath(build_type, 'host_forwarder') |
130 assert os.path.exists(host_forwarder_path), 'Please build forwarder2' | 159 assert os.path.exists(host_forwarder_path), 'Please build forwarder2' |
131 (exit_code, output) = cmd_helper.GetCmdStatusAndOutput( | 160 (exit_code, output) = cmd_helper.GetCmdStatusAndOutput( |
132 [host_forwarder_path, 'kill-server']) | 161 [host_forwarder_path, 'kill-server']) |
133 if exit_code != 0: | 162 if exit_code != 0: |
134 (exit_code, output) = cmd_helper.GetCmdStatusAndOutput( | 163 (exit_code, output) = cmd_helper.GetCmdStatusAndOutput( |
135 ['pkill', 'host_forwarder']) | 164 ['pkill', 'host_forwarder']) |
136 if exit_code != 0: | 165 if exit_code != 0: |
137 raise Exception('%s exited with %d:\n%s' % ( | 166 raise Exception('%s exited with %d:\n%s' % ( |
138 host_forwarder_path, exit_code, '\n'.join(output))) | 167 host_forwarder_path, exit_code, '\n'.join(output))) |
139 | 168 |
140 @staticmethod | 169 @staticmethod |
141 def KillDevice(adb, tool): | 170 def KillDevice(adb, tool): |
| 171 """Kills the forwarder process running on the device. |
| 172 |
| 173 Args: |
| 174 adb: Instance of AndroidCommands for talking to the device. |
| 175 tool: Wrapper tool (e.g. valgrind) that can be used to execute the device |
| 176 forwarder (see valgrind_tools.py). |
| 177 """ |
142 logging.info('Killing device_forwarder.') | 178 logging.info('Killing device_forwarder.') |
143 if not adb.FileExistsOnDevice(Forwarder._DEVICE_FORWARDER_PATH): | 179 if not adb.FileExistsOnDevice(Forwarder._DEVICE_FORWARDER_PATH): |
144 return | 180 return |
145 (exit_code, output) = adb.GetShellCommandStatusAndOutput( | 181 (exit_code, output) = adb.GetShellCommandStatusAndOutput( |
146 '%s %s kill-server' % (tool.GetUtilWrapper(), | 182 '%s %s kill-server' % (tool.GetUtilWrapper(), |
147 Forwarder._DEVICE_FORWARDER_PATH)) | 183 Forwarder._DEVICE_FORWARDER_PATH)) |
148 # TODO(pliard): Remove the following call to KillAllBlocking() when we are | 184 # TODO(pliard): Remove the following call to KillAllBlocking() when we are |
149 # sure that the old version of device_forwarder (not supporting | 185 # sure that the old version of device_forwarder (not supporting |
150 # 'kill-server') is not running on the bots anymore. | 186 # 'kill-server') is not running on the bots anymore. |
151 timeout_sec = 5 | 187 timeout_sec = 5 |
152 processes_killed = adb.KillAllBlocking('device_forwarder', timeout_sec) | 188 processes_killed = adb.KillAllBlocking('device_forwarder', timeout_sec) |
153 if not processes_killed: | 189 if not processes_killed: |
154 pids = adb.ExtractPid('device_forwarder') | 190 pids = adb.ExtractPid('device_forwarder') |
155 if pids: | 191 if pids: |
156 raise Exception('Timed out while killing device_forwarder') | 192 raise Exception('Timed out while killing device_forwarder') |
157 | 193 |
158 def DevicePortForHostPort(self, host_port): | 194 def DevicePortForHostPort(self, host_port): |
159 """Get the device port that corresponds to a given host port.""" | 195 """Returns the device port that corresponds to a given host port.""" |
160 return self._host_to_device_port_map.get(host_port) | 196 with self._lock: |
| 197 return self._host_to_device_port_map.get(host_port) |
161 | 198 |
| 199 # Deprecated. |
162 def Close(self): | 200 def Close(self): |
163 """Terminate the forwarder process.""" | 201 """Terminates the forwarder process.""" |
164 if self._device_process: | 202 # TODO(pliard): Remove references in client code. |
165 self._device_process.close() | 203 pass |
166 self._device_process = None | |
OLD | NEW |