blob: 460a3dc97ac0fce75559fb73e13b77a1670b076e [file] [log] [blame]
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import logging
import os
import re
import sys
import time
import android_commands
import cmd_helper
import constants
import ports
from pylib import pexpect
def _MakeBinaryPath(build_type, binary_name):
return os.path.join(cmd_helper.OutDirectory.get(), build_type, binary_name)
class Forwarder(object):
"""Class to manage port forwards from the device to the host."""
# Unix Abstract socket path:
_DEVICE_ADB_CONTROL_PORT = 'chrome_device_forwarder'
_TIMEOUT_SECS = 30
_DEVICE_FORWARDER_PATH = constants.TEST_EXECUTABLE_DIR + '/device_forwarder'
def __init__(self, adb, build_type):
"""Forwards TCP ports on the device back to the host.
Works like adb forward, but in reverse.
Args:
adb: Instance of AndroidCommands for talking to the device.
build_type: 'Release' or 'Debug'.
"""
assert build_type in ('Release', 'Debug')
self._adb = adb
self._host_to_device_port_map = dict()
self._device_process = None
self._host_forwarder_path = _MakeBinaryPath(build_type, 'host_forwarder')
self._device_forwarder_path = _MakeBinaryPath(
build_type, 'device_forwarder')
def Run(self, port_pairs, tool, host_name):
"""Runs the forwarder.
Args:
port_pairs: A list of tuples (device_port, host_port) to forward. Note
that you can specify 0 as a device_port, in which case a
port will by dynamically assigned on the device. You can
get the number of the assigned port using the
DevicePortForHostPort method.
tool: Tool class to use to get wrapper, if necessary, for executing the
forwarder (see valgrind_tools.py).
host_name: Address to forward to, must be addressable from the
host machine. Usually use loopback '127.0.0.1'.
Raises:
Exception on failure to forward the port.
"""
host_adb_control_port = ports.AllocateTestServerPort()
if not host_adb_control_port:
raise Exception('Failed to allocate a TCP port in the host machine.')
self._adb.PushIfNeeded(
self._device_forwarder_path, Forwarder._DEVICE_FORWARDER_PATH)
redirection_commands = [
'%d:%d:%d:%s' % (host_adb_control_port, device, host,
host_name) for device, host in port_pairs]
logging.info('Command format: <ADB port>:<Device port>' +
'[:<Forward to port>:<Forward to address>]')
logging.info('Forwarding using commands: %s', redirection_commands)
if cmd_helper.RunCmd(
['adb', '-s', self._adb._adb.GetSerialNumber(), 'forward',
'tcp:%s' % host_adb_control_port,
'localabstract:%s' % Forwarder._DEVICE_ADB_CONTROL_PORT]) != 0:
raise Exception('Error while running adb forward.')
(exit_code, output) = self._adb.GetShellCommandStatusAndOutput(
'%s %s' % (Forwarder._DEVICE_FORWARDER_PATH,
Forwarder._DEVICE_ADB_CONTROL_PORT))
if exit_code != 0:
raise Exception(
'Failed to start device forwarder:\n%s' % '\n'.join(output))
for redirection_command in redirection_commands:
(exit_code, output) = cmd_helper.GetCmdStatusAndOutput(
[self._host_forwarder_path, redirection_command])
if exit_code != 0:
raise Exception('%s exited with %d:\n%s' % (
self._host_forwarder_path, exit_code, '\n'.join(output)))
tokens = output.split(':')
if len(tokens) != 2:
raise Exception('Unexpected host forwarder output "%s", ' +
'expected "device_port:host_port"' % output)
device_port = int(tokens[0])
host_port = int(tokens[1])
self._host_to_device_port_map[host_port] = device_port
logging.info('Forwarding device port: %d to host port: %d.', device_port,
host_port)
@staticmethod
def KillHost(build_type):
logging.info('Killing host_forwarder.')
host_forwarder_path = _MakeBinaryPath(build_type, 'host_forwarder')
(exit_code, output) = cmd_helper.GetCmdStatusAndOutput(
[host_forwarder_path, 'kill-server'])
if exit_code != 0:
(exit_code, output) = cmd_helper.GetCmdStatusAndOutput(
['pkill', 'host_forwarder'])
if exit_code != 0:
raise Exception('%s exited with %d:\n%s' % (
host_forwarder_path, exit_code, '\n'.join(output)))
@staticmethod
def KillDevice(adb):
logging.info('Killing device_forwarder.')
if not adb.FileExistsOnDevice(Forwarder._DEVICE_FORWARDER_PATH):
return
(exit_code, output) = adb.GetShellCommandStatusAndOutput(
'%s kill-server' % Forwarder._DEVICE_FORWARDER_PATH)
# TODO(pliard): Remove the following call to KillAllBlocking() when we are
# sure that the old version of device_forwarder (not supporting
# 'kill-server') is not running on the bots anymore.
timeout_sec = 5
processes_killed = adb.KillAllBlocking('device_forwarder', timeout_sec)
if not processes_killed:
pids = adb.ExtractPid('device_forwarder')
if pids:
raise Exception('Timed out while killing device_forwarder')
def DevicePortForHostPort(self, host_port):
"""Get the device port that corresponds to a given host port."""
return self._host_to_device_port_map.get(host_port)
def Close(self):
"""Terminate the forwarder process."""
if self._device_process:
self._device_process.close()
self._device_process = None