Wed, 31 Dec 2014 13:27:57 +0100
Ignore runtime configuration files generated during quality assurance.
michael@0 | 1 | # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
michael@0 | 2 | # Use of this source code is governed by a BSD-style license that can be |
michael@0 | 3 | # found in the LICENSE file. |
michael@0 | 4 | |
michael@0 | 5 | import logging |
michael@0 | 6 | import os |
michael@0 | 7 | import re |
michael@0 | 8 | import sys |
michael@0 | 9 | import time |
michael@0 | 10 | |
michael@0 | 11 | import android_commands |
michael@0 | 12 | import cmd_helper |
michael@0 | 13 | import constants |
michael@0 | 14 | import ports |
michael@0 | 15 | |
michael@0 | 16 | from pylib import pexpect |
michael@0 | 17 | |
michael@0 | 18 | class Forwarder(object): |
michael@0 | 19 | """Class to manage port forwards from the device to the host.""" |
michael@0 | 20 | |
michael@0 | 21 | _DEVICE_FORWARDER_PATH = constants.TEST_EXECUTABLE_DIR + '/device_forwarder' |
michael@0 | 22 | |
michael@0 | 23 | # Unix Abstract socket path: |
michael@0 | 24 | _DEVICE_ADB_CONTROL_PORT = 'chrome_device_forwarder' |
michael@0 | 25 | _TIMEOUT_SECS = 30 |
michael@0 | 26 | |
michael@0 | 27 | def __init__(self, adb, port_pairs, tool, host_name, build_type): |
michael@0 | 28 | """Forwards TCP ports on the device back to the host. |
michael@0 | 29 | |
michael@0 | 30 | Works like adb forward, but in reverse. |
michael@0 | 31 | |
michael@0 | 32 | Args: |
michael@0 | 33 | adb: Instance of AndroidCommands for talking to the device. |
michael@0 | 34 | port_pairs: A list of tuples (device_port, host_port) to forward. Note |
michael@0 | 35 | that you can specify 0 as a device_port, in which case a |
michael@0 | 36 | port will by dynamically assigned on the device. You can |
michael@0 | 37 | get the number of the assigned port using the |
michael@0 | 38 | DevicePortForHostPort method. |
michael@0 | 39 | tool: Tool class to use to get wrapper, if necessary, for executing the |
michael@0 | 40 | forwarder (see valgrind_tools.py). |
michael@0 | 41 | host_name: Address to forward to, must be addressable from the |
michael@0 | 42 | host machine. Usually use loopback '127.0.0.1'. |
michael@0 | 43 | build_type: 'Release' or 'Debug'. |
michael@0 | 44 | |
michael@0 | 45 | Raises: |
michael@0 | 46 | Exception on failure to forward the port. |
michael@0 | 47 | """ |
michael@0 | 48 | self._adb = adb |
michael@0 | 49 | self._host_to_device_port_map = dict() |
michael@0 | 50 | self._host_process = None |
michael@0 | 51 | self._device_process = None |
michael@0 | 52 | self._adb_forward_process = None |
michael@0 | 53 | |
michael@0 | 54 | self._host_adb_control_port = ports.AllocateTestServerPort() |
michael@0 | 55 | if not self._host_adb_control_port: |
michael@0 | 56 | raise Exception('Failed to allocate a TCP port in the host machine.') |
michael@0 | 57 | adb.PushIfNeeded( |
michael@0 | 58 | os.path.join(constants.CHROME_DIR, 'out', build_type, |
michael@0 | 59 | 'device_forwarder'), |
michael@0 | 60 | Forwarder._DEVICE_FORWARDER_PATH) |
michael@0 | 61 | self._host_forwarder_path = os.path.join(constants.CHROME_DIR, |
michael@0 | 62 | 'out', |
michael@0 | 63 | build_type, |
michael@0 | 64 | 'host_forwarder') |
michael@0 | 65 | forward_string = ['%d:%d:%s' % |
michael@0 | 66 | (device, host, host_name) for device, host in port_pairs] |
michael@0 | 67 | logging.info('Forwarding ports: %s', forward_string) |
michael@0 | 68 | timeout_sec = 5 |
michael@0 | 69 | host_pattern = 'host_forwarder.*' + ' '.join(forward_string) |
michael@0 | 70 | # TODO(felipeg): Rather than using a blocking kill() here, the device |
michael@0 | 71 | # forwarder could try to bind the Unix Domain Socket until it succeeds or |
michael@0 | 72 | # while it fails because the socket is already bound (with appropriate |
michael@0 | 73 | # timeout handling obviously). |
michael@0 | 74 | self._KillHostForwarderBlocking(host_pattern, timeout_sec) |
michael@0 | 75 | self._KillDeviceForwarderBlocking(timeout_sec) |
michael@0 | 76 | self._adb_forward_process = pexpect.spawn( |
michael@0 | 77 | 'adb', ['-s', |
michael@0 | 78 | adb._adb.GetSerialNumber(), |
michael@0 | 79 | 'forward', |
michael@0 | 80 | 'tcp:%s' % self._host_adb_control_port, |
michael@0 | 81 | 'localabstract:%s' % Forwarder._DEVICE_ADB_CONTROL_PORT]) |
michael@0 | 82 | self._device_process = pexpect.spawn( |
michael@0 | 83 | 'adb', ['-s', |
michael@0 | 84 | adb._adb.GetSerialNumber(), |
michael@0 | 85 | 'shell', |
michael@0 | 86 | '%s %s -D --adb_sock=%s' % ( |
michael@0 | 87 | tool.GetUtilWrapper(), |
michael@0 | 88 | Forwarder._DEVICE_FORWARDER_PATH, |
michael@0 | 89 | Forwarder._DEVICE_ADB_CONTROL_PORT)]) |
michael@0 | 90 | |
michael@0 | 91 | device_success_re = re.compile('Starting Device Forwarder.') |
michael@0 | 92 | device_failure_re = re.compile('.*:ERROR:(.*)') |
michael@0 | 93 | index = self._device_process.expect([device_success_re, |
michael@0 | 94 | device_failure_re, |
michael@0 | 95 | pexpect.EOF, |
michael@0 | 96 | pexpect.TIMEOUT], |
michael@0 | 97 | Forwarder._TIMEOUT_SECS) |
michael@0 | 98 | if index == 1: |
michael@0 | 99 | # Failure |
michael@0 | 100 | error_msg = str(self._device_process.match.group(1)) |
michael@0 | 101 | logging.error(self._device_process.before) |
michael@0 | 102 | self._CloseProcess() |
michael@0 | 103 | raise Exception('Failed to start Device Forwarder with Error: %s' % |
michael@0 | 104 | error_msg) |
michael@0 | 105 | elif index == 2: |
michael@0 | 106 | logging.error(self._device_process.before) |
michael@0 | 107 | self._CloseProcess() |
michael@0 | 108 | raise Exception('Unexpected EOF while trying to start Device Forwarder.') |
michael@0 | 109 | elif index == 3: |
michael@0 | 110 | logging.error(self._device_process.before) |
michael@0 | 111 | self._CloseProcess() |
michael@0 | 112 | raise Exception('Timeout while trying start Device Forwarder') |
michael@0 | 113 | |
michael@0 | 114 | self._host_process = pexpect.spawn(self._host_forwarder_path, |
michael@0 | 115 | ['--adb_port=%s' % ( |
michael@0 | 116 | self._host_adb_control_port)] + |
michael@0 | 117 | forward_string) |
michael@0 | 118 | |
michael@0 | 119 | # Read the output of the command to determine which device ports where |
michael@0 | 120 | # forwarded to which host ports (necessary if |
michael@0 | 121 | host_success_re = re.compile('Forwarding device port (\d+) to host (\d+):') |
michael@0 | 122 | host_failure_re = re.compile('Couldn\'t start forwarder server for port ' |
michael@0 | 123 | 'spec: (\d+):(\d+)') |
michael@0 | 124 | for pair in port_pairs: |
michael@0 | 125 | index = self._host_process.expect([host_success_re, |
michael@0 | 126 | host_failure_re, |
michael@0 | 127 | pexpect.EOF, |
michael@0 | 128 | pexpect.TIMEOUT], |
michael@0 | 129 | Forwarder._TIMEOUT_SECS) |
michael@0 | 130 | if index == 0: |
michael@0 | 131 | # Success |
michael@0 | 132 | device_port = int(self._host_process.match.group(1)) |
michael@0 | 133 | host_port = int(self._host_process.match.group(2)) |
michael@0 | 134 | self._host_to_device_port_map[host_port] = device_port |
michael@0 | 135 | logging.info("Forwarding device port: %d to host port: %d." % |
michael@0 | 136 | (device_port, host_port)) |
michael@0 | 137 | elif index == 1: |
michael@0 | 138 | # Failure |
michael@0 | 139 | device_port = int(self._host_process.match.group(1)) |
michael@0 | 140 | host_port = int(self._host_process.match.group(2)) |
michael@0 | 141 | self._CloseProcess() |
michael@0 | 142 | raise Exception('Failed to forward port %d to %d' % (device_port, |
michael@0 | 143 | host_port)) |
michael@0 | 144 | elif index == 2: |
michael@0 | 145 | logging.error(self._host_process.before) |
michael@0 | 146 | self._CloseProcess() |
michael@0 | 147 | raise Exception('Unexpected EOF while trying to forward ports %s' % |
michael@0 | 148 | port_pairs) |
michael@0 | 149 | elif index == 3: |
michael@0 | 150 | logging.error(self._host_process.before) |
michael@0 | 151 | self._CloseProcess() |
michael@0 | 152 | raise Exception('Timeout while trying to forward ports %s' % port_pairs) |
michael@0 | 153 | |
michael@0 | 154 | def _KillHostForwarderBlocking(self, host_pattern, timeout_sec): |
michael@0 | 155 | """Kills any existing host forwarders using the provided pattern. |
michael@0 | 156 | |
michael@0 | 157 | Note that this waits until the process terminates. |
michael@0 | 158 | """ |
michael@0 | 159 | cmd_helper.RunCmd(['pkill', '-f', host_pattern]) |
michael@0 | 160 | elapsed = 0 |
michael@0 | 161 | wait_period = 0.1 |
michael@0 | 162 | while not cmd_helper.RunCmd(['pgrep', '-f', host_pattern]) and ( |
michael@0 | 163 | elapsed < timeout_sec): |
michael@0 | 164 | time.sleep(wait_period) |
michael@0 | 165 | elapsed += wait_period |
michael@0 | 166 | if elapsed >= timeout_sec: |
michael@0 | 167 | raise Exception('Timed out while killing ' + host_pattern) |
michael@0 | 168 | |
michael@0 | 169 | def _KillDeviceForwarderBlocking(self, timeout_sec): |
michael@0 | 170 | """Kills any existing device forwarders. |
michael@0 | 171 | |
michael@0 | 172 | Note that this waits until the process terminates. |
michael@0 | 173 | """ |
michael@0 | 174 | processes_killed = self._adb.KillAllBlocking( |
michael@0 | 175 | 'device_forwarder', timeout_sec) |
michael@0 | 176 | if not processes_killed: |
michael@0 | 177 | pids = self._adb.ExtractPid('device_forwarder') |
michael@0 | 178 | if pids: |
michael@0 | 179 | raise Exception('Timed out while killing device_forwarder') |
michael@0 | 180 | |
michael@0 | 181 | def _CloseProcess(self): |
michael@0 | 182 | if self._host_process: |
michael@0 | 183 | self._host_process.close() |
michael@0 | 184 | if self._device_process: |
michael@0 | 185 | self._device_process.close() |
michael@0 | 186 | if self._adb_forward_process: |
michael@0 | 187 | self._adb_forward_process.close() |
michael@0 | 188 | self._host_process = None |
michael@0 | 189 | self._device_process = None |
michael@0 | 190 | self._adb_forward_process = None |
michael@0 | 191 | |
michael@0 | 192 | def DevicePortForHostPort(self, host_port): |
michael@0 | 193 | """Get the device port that corresponds to a given host port.""" |
michael@0 | 194 | return self._host_to_device_port_map.get(host_port) |
michael@0 | 195 | |
michael@0 | 196 | def Close(self): |
michael@0 | 197 | """Terminate the forwarder process.""" |
michael@0 | 198 | self._CloseProcess() |