xref: /aosp_15_r20/external/cronet/build/fuchsia/test/test_server.py (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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