Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(963)

Side by Side Diff: build/android/pylib/forwarder.py

Issue 18086004: Move Python setup/tear down logic into Forwarder itself. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 7 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
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 threading
10 import time 10 import time
11 11
12 import android_commands 12 import android_commands
13 import cmd_helper 13 import cmd_helper
14 import constants 14 import constants
15 15
16 from pylib import pexpect 16 from pylib import valgrind_tools
17 17
18 18
19 def _MakeBinaryPath(build_type, binary_name): 19 def _MakeBinaryPath(build_type, binary_name):
20 return os.path.join(cmd_helper.OutDirectory.get(), build_type, binary_name) 20 return os.path.join(cmd_helper.OutDirectory.get(), build_type, binary_name)
21 21
22 22
23 class Forwarder(object): 23 class Forwarder(object):
24 """Thread-safe class to manage port forwards from the device to the host.""" 24 """Thread-safe class to manage port forwards from the device to the host."""
25 25
26 _DEVICE_FORWARDER_FOLDER = (constants.TEST_EXECUTABLE_DIR + 26 _DEVICE_FORWARDER_FOLDER = (constants.TEST_EXECUTABLE_DIR +
27 '/forwarder/') 27 '/forwarder/')
28 _DEVICE_FORWARDER_PATH = (constants.TEST_EXECUTABLE_DIR + 28 _DEVICE_FORWARDER_PATH = (constants.TEST_EXECUTABLE_DIR +
29 '/forwarder/device_forwarder') 29 '/forwarder/device_forwarder')
30 _LD_LIBRARY_PATH = 'LD_LIBRARY_PATH=%s' % _DEVICE_FORWARDER_FOLDER 30 _LD_LIBRARY_PATH = 'LD_LIBRARY_PATH=%s' % _DEVICE_FORWARDER_FOLDER
31 31
32 def __init__(self, adb, build_type): 32 _lock = threading.Lock()
33 """Forwards TCP ports on the device back to the host. 33 _instance = None
34 34
35 Works like adb forward, but in reverse. 35 @staticmethod
36 36 def Map(port_pairs, adb, build_type='Debug', tool=None):
37 Args:
38 adb: Instance of AndroidCommands for talking to the device.
39 build_type: 'Release' or 'Debug'.
40 """
41 assert build_type in ('Release', 'Debug')
42 self._adb = adb
43 self._device_to_host_port_map = dict()
44 self._host_to_device_port_map = dict()
45 self._device_initialized = False
46 self._host_adb_control_port = 0
47 self._lock = threading.Lock()
48 self._host_forwarder_path = _MakeBinaryPath(build_type, 'host_forwarder')
49 self._device_forwarder_path_on_host = os.path.join(
50 cmd_helper.OutDirectory.get(), build_type, 'forwarder_dist')
51
52 def Run(self, port_pairs, tool):
53 """Runs the forwarder. 37 """Runs the forwarder.
54 38
55 Args: 39 Args:
56 port_pairs: A list of tuples (device_port, host_port) to forward. Note 40 port_pairs: A list of tuples (device_port, host_port) to forward. Note
57 that you can specify 0 as a device_port, in which case a 41 that you can specify 0 as a device_port, in which case a
58 port will by dynamically assigned on the device. You can 42 port will by dynamically assigned on the device. You can
59 get the number of the assigned port using the 43 get the number of the assigned port using the
60 DevicePortForHostPort method. 44 DevicePortForHostPort method.
45 adb: An AndroidCommands instance.
61 tool: Tool class to use to get wrapper, if necessary, for executing the 46 tool: Tool class to use to get wrapper, if necessary, for executing the
62 forwarder (see valgrind_tools.py). 47 forwarder (see valgrind_tools.py).
63 48
64 Raises: 49 Raises:
65 Exception on failure to forward the port. 50 Exception on failure to forward the port.
66 """ 51 """
67 with self._lock: 52 if not tool:
68 self._InitDeviceLocked(tool) 53 tool = valgrind_tools.CreateTool()
69 host_name = '127.0.0.1' 54 with Forwarder._lock:
55 instance = Forwarder._GetInstanceLocked(build_type, tool)
56 instance._InitDeviceOnceLocked(adb, tool)
57
70 redirection_commands = [ 58 redirection_commands = [
71 ['--serial-id=' + self._adb.Adb().GetSerialNumber(), '--map', 59 ['--serial-id=' + adb.Adb().GetSerialNumber(), '--map',
72 str(device), str(host)] for device, host in port_pairs] 60 str(device), str(host)] for device, host in port_pairs]
73 logging.info('Forwarding using commands: %s', redirection_commands) 61 logging.info('Forwarding using commands: %s', redirection_commands)
74 62
75 for redirection_command in redirection_commands: 63 for redirection_command in redirection_commands:
76 try: 64 try:
77 (exit_code, output) = cmd_helper.GetCmdStatusAndOutput( 65 (exit_code, output) = cmd_helper.GetCmdStatusAndOutput(
78 [self._host_forwarder_path] + redirection_command) 66 [instance._host_forwarder_path] + redirection_command)
79 except OSError as e: 67 except OSError as e:
80 if e.errno == 2: 68 if e.errno == 2:
81 raise Exception('Unable to start host forwarder. Make sure you have' 69 raise Exception('Unable to start host forwarder. Make sure you have'
82 ' built host_forwarder.') 70 ' built host_forwarder.')
83 else: raise 71 else: raise
84 if exit_code != 0: 72 if exit_code != 0:
85 raise Exception('%s exited with %d:\n%s' % ( 73 raise Exception('%s exited with %d:\n%s' % (
86 self._host_forwarder_path, exit_code, '\n'.join(output))) 74 instance._host_forwarder_path, exit_code, '\n'.join(output)))
87 tokens = output.split(':') 75 tokens = output.split(':')
88 if len(tokens) != 2: 76 if len(tokens) != 2:
89 raise Exception(('Unexpected host forwarder output "%s", ' + 77 raise Exception(('Unexpected host forwarder output "%s", ' +
90 'expected "device_port:host_port"') % output) 78 'expected "device_port:host_port"') % output)
91 device_port = int(tokens[0]) 79 device_port = int(tokens[0])
92 host_port = int(tokens[1]) 80 host_port = int(tokens[1])
93 self._device_to_host_port_map[device_port] = host_port 81 instance._device_to_host_port_map[device_port] = host_port
94 self._host_to_device_port_map[host_port] = device_port 82 instance._host_to_device_port_map[host_port] = device_port
95 logging.info('Forwarding device port: %d to host port: %d.', 83 logging.info('Forwarding device port: %d to host port: %d.',
96 device_port, host_port) 84 device_port, host_port)
97 85
98 def _InitDeviceLocked(self, tool): 86 @staticmethod
99 """Initializes the device forwarder process (only once).""" 87 def UnmapDevicePort(device_port, adb):
frankf 2013/07/10 18:15:38 Is it too dangerous to add a convenience method to
Philippe 2013/07/11 09:34:06 Good idea.
100 if self._device_initialized: 88 """Unmaps a previously forwarded device port.
89
90 Args:
91 device_port: A previously forwarded port (through Map()).
92 """
93 with Forwarder._lock:
94 Forwarder._GetInstanceLocked(None, None)._UnmapDevicePortInternalLocked(
frankf 2013/07/10 18:15:38 What happens if Unmap is called before Map?
Philippe 2013/07/11 09:34:06 An error would be logged but no exception would be
95 device_port, adb)
96
97 @staticmethod
98 def DevicePortForHostPort(host_port):
99 """Returns the device port that corresponds to a given host port."""
100 with Forwarder._lock:
101 return Forwarder._GetInstanceLocked(
102 None, None)._host_to_device_port_map.get(host_port)
103
104 @staticmethod
105 def _GetInstanceLocked(build_type, tool):
106 if not Forwarder._instance:
107 Forwarder._instance = Forwarder(build_type, tool)
108 return Forwarder._instance
109
110 def __init__(self, build_type, tool):
frankf 2013/07/10 18:15:38 This is private and should be called once, maybe d
Philippe 2013/07/11 09:34:06 Good idea.
111 self._build_type = build_type
112 self._tool = tool
113 self._initialized_devices = set()
114 self._device_to_host_port_map = dict()
115 self._host_to_device_port_map = dict()
116 self._InitHostOnceLocked()
117 self._device_forwarder_path_on_host = os.path.join(
118 cmd_helper.OutDirectory.get(), self._build_type, 'forwarder_dist')
119
120 def _InitHostOnceLocked(self):
121 self._host_forwarder_path = _MakeBinaryPath(
122 self._build_type, 'host_forwarder')
123 if not os.path.exists(self._host_forwarder_path):
124 self._build_type = 'Release' if self._build_type == 'Debug' else 'Debug'
frankf 2013/07/10 18:15:38 Why don't we know the definite path to the binary?
Philippe 2013/07/11 09:34:06 In theory we do if the client provides the build_t
125 self._host_forwarder_path = _MakeBinaryPath(
126 self._build_type, 'host_forwarder')
127 assert os.path.exists(
128 self._host_forwarder_path), 'Please build forwarder2'
129 self._KillHost()
130
131 def _InitDeviceOnceLocked(self, adb, tool):
frankf 2013/07/10 18:15:38 Can you add brief docstrings for all methods in th
Philippe 2013/07/11 09:34:06 Done.
132 device_serial = adb.Adb().GetSerialNumber()
133 if device_serial in self._initialized_devices:
101 return 134 return
102 self._adb.PushIfNeeded( 135 Forwarder._KillDevice(adb, tool)
136 adb.PushIfNeeded(
103 self._device_forwarder_path_on_host, 137 self._device_forwarder_path_on_host,
104 Forwarder._DEVICE_FORWARDER_FOLDER) 138 Forwarder._DEVICE_FORWARDER_FOLDER)
105 (exit_code, output) = self._adb.GetShellCommandStatusAndOutput( 139 (exit_code, output) = adb.GetShellCommandStatusAndOutput(
106 '%s %s %s' % (Forwarder._LD_LIBRARY_PATH, tool.GetUtilWrapper(), 140 '%s %s %s' % (Forwarder._LD_LIBRARY_PATH, tool.GetUtilWrapper(),
107 Forwarder._DEVICE_FORWARDER_PATH)) 141 Forwarder._DEVICE_FORWARDER_PATH))
108 if exit_code != 0: 142 if exit_code != 0:
109 raise Exception( 143 raise Exception(
110 'Failed to start device forwarder:\n%s' % '\n'.join(output)) 144 'Failed to start device forwarder:\n%s' % '\n'.join(output))
111 self._device_initialized = True 145 self._initialized_devices.add(device_serial)
112 146
113 def UnmapDevicePort(self, device_port): 147 def _UnmapDevicePortInternalLocked(self, device_port, adb):
Philippe 2013/07/11 09:34:06 FYI, I inlined this method in the latest patch set
114 """Unmaps a previously forwarded device port.
115
116 Args:
117 device_port: A previously forwarded port (through Run()).
118 """
119 with self._lock:
120 self._UnmapDevicePortInternalLocked(device_port)
121
122 def _UnmapDevicePortInternalLocked(self, device_port):
123 if not device_port in self._device_to_host_port_map: 148 if not device_port in self._device_to_host_port_map:
124 return 149 return
125 redirection_command = [ 150 redirection_command = [
126 '--serial-id=' + self._adb.Adb().GetSerialNumber(), '--unmap', 151 '--serial-id=' + adb.Adb().GetSerialNumber(), '--unmap',
127 str(device_port)] 152 str(device_port)]
128 (exit_code, output) = cmd_helper.GetCmdStatusAndOutput( 153 (exit_code, output) = cmd_helper.GetCmdStatusAndOutput(
129 [self._host_forwarder_path] + redirection_command) 154 [self._host_forwarder_path] + redirection_command)
130 if exit_code != 0: 155 if exit_code != 0:
131 logging.error('%s exited with %d:\n%s' % ( 156 logging.error('%s exited with %d:\n%s' % (
132 self._host_forwarder_path, exit_code, '\n'.join(output))) 157 self._host_forwarder_path, exit_code, '\n'.join(output)))
133 host_port = self._device_to_host_port_map[device_port] 158 host_port = self._device_to_host_port_map[device_port]
134 del self._device_to_host_port_map[device_port] 159 del self._device_to_host_port_map[device_port]
135 del self._host_to_device_port_map[host_port] 160 del self._host_to_device_port_map[host_port]
136 161
137 @staticmethod 162 def _KillHost(self):
138 def KillHost(build_type='Debug'): 163 """Kills the forwarder process running on the host."""
139 """Kills the forwarder process running on the host.
140
141 Args:
142 build_type: 'Release' or 'Debug' (default='Debug')
143 """
144 logging.info('Killing host_forwarder.') 164 logging.info('Killing host_forwarder.')
145 host_forwarder_path = _MakeBinaryPath(build_type, 'host_forwarder')
146 if not os.path.exists(host_forwarder_path):
147 host_forwarder_path = _MakeBinaryPath(
148 'Release' if build_type == 'Debug' else 'Debug', 'host_forwarder')
149 assert os.path.exists(host_forwarder_path), 'Please build forwarder2'
150 (exit_code, output) = cmd_helper.GetCmdStatusAndOutput( 165 (exit_code, output) = cmd_helper.GetCmdStatusAndOutput(
151 [host_forwarder_path, '--kill-server']) 166 [self._host_forwarder_path, '--kill-server'])
152 if exit_code != 0: 167 if exit_code != 0:
153 (exit_code, output) = cmd_helper.GetCmdStatusAndOutput( 168 (exit_code, output) = cmd_helper.GetCmdStatusAndOutput(
154 ['pkill', 'host_forwarder']) 169 ['pkill', '-9', 'host_forwarder'])
155 if exit_code != 0: 170 if exit_code != 0:
156 raise Exception('%s exited with %d:\n%s' % ( 171 raise Exception('%s exited with %d:\n%s' % (
157 host_forwarder_path, exit_code, '\n'.join(output))) 172 self._host_forwarder_path, exit_code, '\n'.join(output)))
158 173
159 @staticmethod 174 @staticmethod
160 def KillDevice(adb, tool): 175 def _KillDevice(adb, tool):
161 """Kills the forwarder process running on the device. 176 """Kills the forwarder process running on the device.
162 177
163 Args: 178 Args:
164 adb: Instance of AndroidCommands for talking to the device. 179 adb: Instance of AndroidCommands for talking to the device.
165 tool: Wrapper tool (e.g. valgrind) that can be used to execute the device 180 tool: Wrapper tool (e.g. valgrind) that can be used to execute the device
166 forwarder (see valgrind_tools.py). 181 forwarder (see valgrind_tools.py).
167 """ 182 """
168 logging.info('Killing device_forwarder.') 183 logging.info('Killing device_forwarder.')
169 if not adb.FileExistsOnDevice(Forwarder._DEVICE_FORWARDER_PATH): 184 if not adb.FileExistsOnDevice(Forwarder._DEVICE_FORWARDER_PATH):
170 return 185 return
171 (exit_code, output) = adb.GetShellCommandStatusAndOutput( 186 (exit_code, output) = adb.GetShellCommandStatusAndOutput(
172 '%s %s --kill-server' % (tool.GetUtilWrapper(), 187 '%s %s --kill-server' % (tool.GetUtilWrapper(),
173 Forwarder._DEVICE_FORWARDER_PATH)) 188 Forwarder._DEVICE_FORWARDER_PATH))
174 # TODO(pliard): Remove the following call to KillAllBlocking() when we are 189 # TODO(pliard): Remove the following call to KillAllBlocking() when we are
175 # sure that the old version of device_forwarder (not supporting 190 # sure that the old version of device_forwarder (not supporting
176 # 'kill-server') is not running on the bots anymore. 191 # 'kill-server') is not running on the bots anymore.
177 timeout_sec = 5 192 timeout_sec = 5
178 processes_killed = adb.KillAllBlocking('device_forwarder', timeout_sec) 193 processes_killed = adb.KillAllBlocking('device_forwarder', timeout_sec)
179 if not processes_killed: 194 if not processes_killed:
180 pids = adb.ExtractPid('device_forwarder') 195 pids = adb.ExtractPid('device_forwarder')
181 if pids: 196 if pids:
182 raise Exception('Timed out while killing device_forwarder') 197 raise Exception('Timed out while killing device_forwarder')
183
184 def DevicePortForHostPort(self, host_port):
185 """Returns the device port that corresponds to a given host port."""
186 with self._lock:
187 return self._host_to_device_port_map.get(host_port)
188
189 def Close(self):
190 """Releases the previously forwarded ports."""
191 with self._lock:
192 for device_port in self._device_to_host_port_map.copy():
193 self._UnmapDevicePortInternalLocked(device_port)
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698