1*6777b538SAndroid Build Coastguard Worker# Copyright 2022 The Chromium Authors 2*6777b538SAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be 3*6777b538SAndroid Build Coastguard Worker# found in the LICENSE file. 4*6777b538SAndroid Build Coastguard Worker"""Test server set up.""" 5*6777b538SAndroid Build Coastguard Worker 6*6777b538SAndroid Build Coastguard Workerimport logging 7*6777b538SAndroid Build Coastguard Workerimport os 8*6777b538SAndroid Build Coastguard Workerimport sys 9*6777b538SAndroid Build Coastguard Workerimport subprocess 10*6777b538SAndroid Build Coastguard Worker 11*6777b538SAndroid Build Coastguard Workerfrom typing import List, Optional, Tuple 12*6777b538SAndroid Build Coastguard Worker 13*6777b538SAndroid Build Coastguard Workerfrom common import DIR_SRC_ROOT, run_ffx_command 14*6777b538SAndroid Build Coastguard Workerfrom compatible_utils import get_ssh_prefix 15*6777b538SAndroid Build Coastguard Worker 16*6777b538SAndroid Build Coastguard Workersys.path.append(os.path.join(DIR_SRC_ROOT, 'build', 'util', 'lib', 'common')) 17*6777b538SAndroid Build Coastguard Worker# pylint: disable=import-error,wrong-import-position 18*6777b538SAndroid Build Coastguard Workerimport chrome_test_server_spawner 19*6777b538SAndroid Build Coastguard Worker# pylint: enable=import-error,wrong-import-position 20*6777b538SAndroid Build Coastguard Worker 21*6777b538SAndroid Build Coastguard Worker 22*6777b538SAndroid Build Coastguard Workerdef port_forward(host_port_pair: str, host_port: int) -> int: 23*6777b538SAndroid Build Coastguard Worker """Establishes a port forwarding SSH task to a localhost TCP endpoint 24*6777b538SAndroid Build Coastguard Worker hosted at port |local_port|. Blocks until port forwarding is established. 25*6777b538SAndroid Build Coastguard Worker 26*6777b538SAndroid Build Coastguard Worker Returns the remote port number.""" 27*6777b538SAndroid Build Coastguard Worker 28*6777b538SAndroid Build Coastguard Worker ssh_prefix = get_ssh_prefix(host_port_pair) 29*6777b538SAndroid Build Coastguard Worker 30*6777b538SAndroid Build Coastguard Worker # Allow a tunnel to be established. 31*6777b538SAndroid Build Coastguard Worker subprocess.run(ssh_prefix + ['echo', 'true'], check=True) 32*6777b538SAndroid Build Coastguard Worker 33*6777b538SAndroid Build Coastguard Worker forward_cmd = [ 34*6777b538SAndroid Build Coastguard Worker '-O', 35*6777b538SAndroid Build Coastguard Worker 'forward', # Send SSH mux control signal. 36*6777b538SAndroid Build Coastguard Worker '-R', 37*6777b538SAndroid Build Coastguard Worker '0:localhost:%d' % host_port, 38*6777b538SAndroid Build Coastguard Worker '-v', # Get forwarded port info from stderr. 39*6777b538SAndroid Build Coastguard Worker '-NT' # Don't execute command; don't allocate terminal. 40*6777b538SAndroid Build Coastguard Worker ] 41*6777b538SAndroid Build Coastguard Worker forward_proc = subprocess.run(ssh_prefix + forward_cmd, 42*6777b538SAndroid Build Coastguard Worker capture_output=True, 43*6777b538SAndroid Build Coastguard Worker check=False, 44*6777b538SAndroid Build Coastguard Worker text=True) 45*6777b538SAndroid Build Coastguard Worker if forward_proc.returncode != 0: 46*6777b538SAndroid Build Coastguard Worker raise Exception( 47*6777b538SAndroid Build Coastguard Worker 'Got an error code when requesting port forwarding: %d' % 48*6777b538SAndroid Build Coastguard Worker forward_proc.returncode) 49*6777b538SAndroid Build Coastguard Worker 50*6777b538SAndroid Build Coastguard Worker output = forward_proc.stdout 51*6777b538SAndroid Build Coastguard Worker parsed_port = int(output.splitlines()[0].strip()) 52*6777b538SAndroid Build Coastguard Worker logging.debug('Port forwarding established (local=%d, device=%d)', 53*6777b538SAndroid Build Coastguard Worker host_port, parsed_port) 54*6777b538SAndroid Build Coastguard Worker return parsed_port 55*6777b538SAndroid Build Coastguard Worker 56*6777b538SAndroid Build Coastguard Worker 57*6777b538SAndroid Build Coastguard Worker# Disable pylint errors since the subclass is not from this directory. 58*6777b538SAndroid Build Coastguard Worker# pylint: disable=invalid-name,missing-function-docstring 59*6777b538SAndroid Build Coastguard Workerclass SSHPortForwarder(chrome_test_server_spawner.PortForwarder): 60*6777b538SAndroid Build Coastguard Worker """Implementation of chrome_test_server_spawner.PortForwarder that uses 61*6777b538SAndroid Build Coastguard Worker SSH's remote port forwarding feature to forward ports.""" 62*6777b538SAndroid Build Coastguard Worker 63*6777b538SAndroid Build Coastguard Worker def __init__(self, host_port_pair: str) -> None: 64*6777b538SAndroid Build Coastguard Worker self._host_port_pair = host_port_pair 65*6777b538SAndroid Build Coastguard Worker 66*6777b538SAndroid Build Coastguard Worker # Maps the host (server) port to the device port number. 67*6777b538SAndroid Build Coastguard Worker self._port_mapping = {} 68*6777b538SAndroid Build Coastguard Worker 69*6777b538SAndroid Build Coastguard Worker def Map(self, port_pairs: List[Tuple[int, int]]) -> None: 70*6777b538SAndroid Build Coastguard Worker for p in port_pairs: 71*6777b538SAndroid Build Coastguard Worker _, host_port = p 72*6777b538SAndroid Build Coastguard Worker self._port_mapping[host_port] = \ 73*6777b538SAndroid Build Coastguard Worker port_forward(self._host_port_pair, host_port) 74*6777b538SAndroid Build Coastguard Worker 75*6777b538SAndroid Build Coastguard Worker def GetDevicePortForHostPort(self, host_port: int) -> int: 76*6777b538SAndroid Build Coastguard Worker return self._port_mapping[host_port] 77*6777b538SAndroid Build Coastguard Worker 78*6777b538SAndroid Build Coastguard Worker def Unmap(self, device_port: int) -> None: 79*6777b538SAndroid Build Coastguard Worker for host_port, entry in self._port_mapping.items(): 80*6777b538SAndroid Build Coastguard Worker if entry == device_port: 81*6777b538SAndroid Build Coastguard Worker ssh_prefix = get_ssh_prefix(self._host_port_pair) 82*6777b538SAndroid Build Coastguard Worker unmap_cmd = [ 83*6777b538SAndroid Build Coastguard Worker '-NT', '-O', 'cancel', '-R', 84*6777b538SAndroid Build Coastguard Worker '0:localhost:%d' % host_port 85*6777b538SAndroid Build Coastguard Worker ] 86*6777b538SAndroid Build Coastguard Worker ssh_proc = subprocess.run(ssh_prefix + unmap_cmd, check=False) 87*6777b538SAndroid Build Coastguard Worker if ssh_proc.returncode != 0: 88*6777b538SAndroid Build Coastguard Worker raise Exception('Error %d when unmapping port %d' % 89*6777b538SAndroid Build Coastguard Worker (ssh_proc.returncode, device_port)) 90*6777b538SAndroid Build Coastguard Worker del self._port_mapping[host_port] 91*6777b538SAndroid Build Coastguard Worker return 92*6777b538SAndroid Build Coastguard Worker 93*6777b538SAndroid Build Coastguard Worker raise Exception('Unmap called for unknown port: %d' % device_port) 94*6777b538SAndroid Build Coastguard Worker 95*6777b538SAndroid Build Coastguard Worker 96*6777b538SAndroid Build Coastguard Worker# pylint: enable=invalid-name,missing-function-docstring 97*6777b538SAndroid Build Coastguard Worker 98*6777b538SAndroid Build Coastguard Worker 99*6777b538SAndroid Build Coastguard Workerdef setup_test_server(target_id: Optional[str], test_concurrency: int)\ 100*6777b538SAndroid Build Coastguard Worker -> Tuple[chrome_test_server_spawner.SpawningServer, str]: 101*6777b538SAndroid Build Coastguard Worker """Provisions a test server and configures |target_id| to use it. 102*6777b538SAndroid Build Coastguard Worker 103*6777b538SAndroid Build Coastguard Worker Args: 104*6777b538SAndroid Build Coastguard Worker target_id: The target to which port forwarding to the test server will 105*6777b538SAndroid Build Coastguard Worker be established. 106*6777b538SAndroid Build Coastguard Worker test_concurrency: The number of parallel test jobs that will be run. 107*6777b538SAndroid Build Coastguard Worker 108*6777b538SAndroid Build Coastguard Worker Returns a tuple of a SpawningServer object and the local url to use on 109*6777b538SAndroid Build Coastguard Worker |target_id| to reach the test server.""" 110*6777b538SAndroid Build Coastguard Worker 111*6777b538SAndroid Build Coastguard Worker logging.debug('Starting test server.') 112*6777b538SAndroid Build Coastguard Worker 113*6777b538SAndroid Build Coastguard Worker host_port_pair = run_ffx_command(cmd=('target', 'get-ssh-address'), 114*6777b538SAndroid Build Coastguard Worker target_id=target_id, 115*6777b538SAndroid Build Coastguard Worker capture_output=True).stdout.strip() 116*6777b538SAndroid Build Coastguard Worker 117*6777b538SAndroid Build Coastguard Worker # The TestLauncher can launch more jobs than the limit specified with 118*6777b538SAndroid Build Coastguard Worker # --test-launcher-jobs so the max number of spawned test servers is set to 119*6777b538SAndroid Build Coastguard Worker # twice that limit here. See https://crbug.com/913156#c19. 120*6777b538SAndroid Build Coastguard Worker spawning_server = chrome_test_server_spawner.SpawningServer( 121*6777b538SAndroid Build Coastguard Worker 0, SSHPortForwarder(host_port_pair), test_concurrency * 2) 122*6777b538SAndroid Build Coastguard Worker 123*6777b538SAndroid Build Coastguard Worker forwarded_port = port_forward(host_port_pair, spawning_server.server_port) 124*6777b538SAndroid Build Coastguard Worker spawning_server.Start() 125*6777b538SAndroid Build Coastguard Worker 126*6777b538SAndroid Build Coastguard Worker logging.debug('Test server listening for connections (port=%d)', 127*6777b538SAndroid Build Coastguard Worker spawning_server.server_port) 128*6777b538SAndroid Build Coastguard Worker logging.debug('Forwarded port is %d', forwarded_port) 129*6777b538SAndroid Build Coastguard Worker 130*6777b538SAndroid Build Coastguard Worker return (spawning_server, 'http://localhost:%d' % forwarded_port) 131