Skip to content

Commit 316ace1

Browse files
camillobruniV8 LUCI CQ
authored andcommitted
Move port-manager code to custom PortManager classes
Move all port-managing methods from Platform to the dedicated PortManager classes. - Add LocalPortManager default implementation - Complete AndroidAdbPortManager implementation - Complete SshPortManager implementation - Add new NumberParser.port_number_zero helper - Make build_ssh_cmd method public so it can be used from the SshPortManager - Add more test PortManagers Change-Id: I5aaccdf0325172586ad42ae345556f68be3d1e14 Reviewed-on: https://chromium-review.googlesource.com/c/crossbench/+/6650994 Commit-Queue: Camillo Bruni <[email protected]> Reviewed-by: Patrick Thier <[email protected]>
1 parent 5f4c2d9 commit 316ace1

File tree

13 files changed

+383
-245
lines changed

13 files changed

+383
-245
lines changed

crossbench/browsers/chromium/devtools.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def connect(self) -> None:
3232
if self._ws and self._ws.connected:
3333
return
3434
try:
35-
self._devtools_port = self._platform.forward_devtools_port(
35+
self._devtools_port = self._platform.ports.forward_devtools(
3636
local_port=self._requested_local_port,
3737
remote_identifier=self._remote_devtools_identifier)
3838
self._ws = websocket.WebSocket()
@@ -59,7 +59,7 @@ def _disconnect_internal(self) -> None:
5959
self._ws = None
6060
if self._devtools_port:
6161
try:
62-
self._platform.stop_port_forward(self._devtools_port)
62+
self._platform.ports.stop_forward(self._devtools_port)
6363
except Exception as e: # pylint: disable=broad-except
6464
# Best effort to remove forwarding, log if it fails but don't crash
6565
logging.warning(

crossbench/parse.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,13 @@ def port_number(cls,
645645
parse_str: bool = True) -> int:
646646
return cls.int_range(1, 65535, name, parse_str)(value)
647647

648+
@classmethod
649+
def port_number_zero(cls,
650+
value: Any,
651+
name: str = "port",
652+
parse_str: bool = True) -> int:
653+
return cls.int_range(0, 65535, name, parse_str)(value)
654+
648655

649656
class LateArgumentError(argparse.ArgumentTypeError):
650657
"""Signals argument parse errors after parser.parse_args().

crossbench/plt/android_adb.py

Lines changed: 45 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616
from snippet_uiautomator import uiautomator
1717
from typing_extensions import override
1818

19-
from crossbench.flags.base import Flags, FlagsData
2019
from crossbench import path as pth
20+
from crossbench.flags.base import Flags, FlagsData
2121
from crossbench.parse import NumberParser
2222
from crossbench.plt.arch import MachineArch
2323
from crossbench.plt.base import SubprocessError
@@ -476,7 +476,50 @@ class AndroidAdbPortManager(PortManager):
476476

477477
def __init__(self, platform: AndroidAdbPlatform, adb: Adb) -> None:
478478
super().__init__(platform)
479-
self._adb = adb
479+
self._adb: Adb = adb
480+
481+
@property
482+
def host_platform(self) -> Platform:
483+
return self._platform.host_platform
484+
485+
@override
486+
def forward(self, local_port: int, remote_port: int) -> int:
487+
local_port = NumberParser.positive_zero_int(local_port, "local_port")
488+
remote_port = NumberParser.port_number(remote_port, "remote_port")
489+
local_port = self._adb.forward(
490+
local_port, remote_port, local_protocol="tcp", remote_protocol="tcp")
491+
logging.debug("Forwarded Remote Port: %s:%s <= %s:%s",
492+
self.host_platform.name, local_port, self, remote_port)
493+
return local_port
494+
495+
@override
496+
def forward_devtools(self, local_port: int, remote_identifier: str) -> int:
497+
local_port = NumberParser.positive_zero_int(local_port, "local_port")
498+
local_port = self._adb.forward(
499+
local=local_port,
500+
remote=remote_identifier,
501+
local_protocol="tcp",
502+
remote_protocol="localabstract")
503+
logging.debug("Forwarded DevTools Port: %s:%s <= %s:%s",
504+
self.host_platform.name, local_port, self, remote_identifier)
505+
return local_port
506+
507+
@override
508+
def stop_forward(self, local_port: int) -> None:
509+
self._adb.forward_remove(local_port, protocol="tcp")
510+
511+
@override
512+
def reverse_forward(self, remote_port: int, local_port: int) -> int:
513+
remote_port = NumberParser.positive_zero_int(remote_port, "remote_port")
514+
local_port = NumberParser.port_number(local_port, "local_port")
515+
remote_port = self._adb.reverse(remote_port, local_port, protocol="tcp")
516+
logging.debug("Forwarded Local Port: %s:%s => %s:%s", self.host_platform,
517+
local_port, self, remote_port)
518+
return remote_port
519+
520+
@override
521+
def stop_reverse_forward(self, remote_port: int) -> None:
522+
self._adb.reverse_remove(remote_port, protocol="tcp")
480523

481524

482525
class AndroidAdbPlatform(RemotePosixPlatform):
@@ -680,45 +723,6 @@ def sh_stdout_bytes(self,
680723
return self.adb.shell_stdout_bytes(
681724
*args, shell=shell, stdin=stdin, env=env, quiet=quiet, check=check)
682725

683-
@override
684-
def port_forward(self, local_port: int, remote_port: int) -> int:
685-
local_port = NumberParser.positive_zero_int(local_port, "local_port")
686-
remote_port = NumberParser.port_number(remote_port, "remote_port")
687-
local_port = self.adb.forward(local_port, remote_port)
688-
logging.debug("Forwarded Remote Port: %s:%s <= %s:%s",
689-
self._host_platform.name, local_port, self, remote_port)
690-
return local_port
691-
692-
@override
693-
def forward_devtools_port(self, local_port: int,
694-
remote_identifier: str) -> int:
695-
local_port = NumberParser.positive_zero_int(local_port, "local_port")
696-
local_port = self.adb.forward(
697-
local=local_port,
698-
remote=remote_identifier,
699-
local_protocol="tcp",
700-
remote_protocol="localabstract")
701-
logging.debug("Forwarded DevTools Port: %s:%s <= %s:%s",
702-
self._host_platform.name, local_port, self, remote_identifier)
703-
return local_port
704-
705-
@override
706-
def stop_port_forward(self, local_port: int) -> None:
707-
self.adb.forward_remove(local_port, protocol="tcp")
708-
709-
@override
710-
def reverse_port_forward(self, remote_port: int, local_port: int) -> int:
711-
remote_port = NumberParser.positive_zero_int(remote_port, "remote_port")
712-
local_port = NumberParser.port_number(local_port, "local_port")
713-
remote_port = self.adb.reverse(remote_port, local_port, protocol="tcp")
714-
logging.debug("Forwarded Local Port: %s:%s => %s:%s", self._host_platform,
715-
local_port, self, remote_port)
716-
return remote_port
717-
718-
@override
719-
def stop_reverse_port_forward(self, remote_port: int) -> None:
720-
self.adb.reverse_remove(remote_port, protocol="tcp")
721-
722726
@override
723727
def pull(self, from_path: pth.AnyPath,
724728
to_path: pth.LocalPath) -> pth.LocalPath:

crossbench/plt/base.py

Lines changed: 3 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@
3535
from crossbench.plt import proc_helper
3636
from crossbench.plt.arch import MachineArch
3737
from crossbench.plt.bin import Binary
38-
from crossbench.plt.port_manager import PortManager, PortScope
38+
from crossbench.plt.port_manager import (LocalPortManager, PortManager,
39+
PortScope)
3940
from crossbench.plt.remote import RemotePopen
4041

4142
if TYPE_CHECKING:
@@ -112,7 +113,7 @@ def __init__(self) -> None:
112113
self._default_port_manager: PortManager = self._create_port_manager()
113114

114115
def _create_port_manager(self) -> PortManager:
115-
return PortManager(self)
116+
return LocalPortManager(self)
116117

117118
def assert_is_local(self) -> None:
118119
if self.is_local:
@@ -577,54 +578,6 @@ def default_tmp_dir(self) -> pth.AnyPath:
577578
def ports(self) -> PortScope:
578579
return self._default_port_manager.scope
579580

580-
def port_forward(self, local_port: int, remote_port: int) -> int:
581-
""" Forwards a device remote_port to a local port."""
582-
# TODO: Migrate forwarding methods to custom PortManager
583-
if remote_port != local_port:
584-
raise ValueError("Cannot forward a remote port on a local platform.")
585-
parse.NumberParser.port_number(local_port, "local_port")
586-
self.assert_is_local()
587-
return local_port
588-
589-
def forward_devtools_port(self, local_port: int,
590-
remote_identifier: str) -> int:
591-
"""Forwards a DevTools debugging port from a remote target to a local port.
592-
593-
Args:
594-
local_port: The local port number to forward to. If 0, a free
595-
port will be chosen by the system.
596-
remote_identifier: A string identifying the remote DevTools socket or
597-
service. For Android, this is typically a
598-
localabstract socket name like
599-
"chrome_devtools_remote".
600-
For other platforms, it might be a remote port number
601-
or other service identifier.
602-
603-
Returns:
604-
The local port number that was actually used for forwarding.
605-
"""
606-
raise NotImplementedError(
607-
f"forward_devtools_port not implemented for {self}")
608-
609-
def stop_port_forward(self, local_port: int) -> None:
610-
# TODO: Migrate forwarding methods to custom PortManager
611-
del local_port
612-
self.assert_is_local()
613-
614-
def reverse_port_forward(self, remote_port: int, local_port: int) -> int:
615-
""" Forwards a local port to a device port."""
616-
# TODO: Migrate forwarding methods to custom PortManager
617-
if remote_port != local_port:
618-
raise ValueError("Cannot forward a remote port on a local platform.")
619-
parse.NumberParser.port_number(remote_port, "remote_port")
620-
self.assert_is_local()
621-
return remote_port
622-
623-
def stop_reverse_port_forward(self, remote_port: int) -> None:
624-
# TODO: Migrate forwarding methods to custom PortManager
625-
del remote_port
626-
self.assert_is_local()
627-
628581
def is_port_used(self, port: int) -> bool:
629582
self.assert_is_local()
630583
for conn in psutil.net_connections(kind="inet"):

crossbench/plt/linux_ssh.py

Lines changed: 4 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,15 @@
44

55
from __future__ import annotations
66

7-
import atexit
8-
import datetime as dt
9-
import logging
107
import shlex
11-
import subprocess
128
from typing import TYPE_CHECKING, Any, Optional
139

1410
from typing_extensions import override
1511

16-
from crossbench import parse
1712
from crossbench.plt.arch import MachineArch
1813
from crossbench.plt.linux import RemoteLinuxPlatform
19-
from crossbench.plt.ssh import SshPlatformMixin, SshPortManager
14+
from crossbench.plt.ssh import SshPlatformMixin
15+
from crossbench.plt.ssh_port_manager import SshPortManager
2016

2117
if TYPE_CHECKING:
2218
from crossbench.path import AnyPath, LocalPath
@@ -26,18 +22,13 @@
2622

2723
class LinuxSshPlatform(SshPlatformMixin, RemoteLinuxPlatform):
2824

29-
PORT_FORWARDING_TIMEOUT = dt.timedelta(seconds=10)
3025

3126
def __init__(self, host_platform: Platform, host: str, port: int,
3227
ssh_port: int, ssh_user: str) -> None:
3328
super().__init__(host_platform, host, port, ssh_port, ssh_user)
3429
self._machine: MachineArch | None = None
3530
self._system_details: dict[str, Any] | None = None
3631
self._cpu_details: dict[str, Any] | None = None
37-
# TOOO: create custom PortManager for linux-ssh
38-
self._port_forward_popens: dict[int, subprocess.Popen] = {}
39-
self._reverse_port_forward_popens: dict[int, subprocess.Popen] = {}
40-
atexit.register(self._stop_all_port_forward)
4132

4233
def _create_port_manager(self) -> PortManager:
4334
return SshPortManager(self)
@@ -47,7 +38,7 @@ def _create_port_manager(self) -> PortManager:
4738
def name(self) -> str:
4839
return "linux_ssh"
4940

50-
def _build_ssh_cmd(self, *args: CmdArg, shell: bool = False) -> ListCmdArgs:
41+
def build_ssh_cmd(self, *args: CmdArg, shell: bool = False) -> ListCmdArgs:
5142
self.validate_shell_args(args, shell)
5243
ssh_cmd: ListCmdArgs = [
5344
"ssh", "-p", f"{self._ssh_port}", f"{self._ssh_user}@{self._host}"
@@ -64,7 +55,7 @@ def _build_ssh_cmd(self, *args: CmdArg, shell: bool = False) -> ListCmdArgs:
6455

6556
@override
6657
def build_shell_cmd(self, *args: CmdArg, shell: bool = False) -> ListCmdArgs:
67-
return self._build_ssh_cmd(*args, shell=shell)
58+
return self.build_ssh_cmd(*args, shell=shell)
6859

6960
def processes(self,
7061
attrs: Optional[list[str]] = None) -> list[dict[str, Any]]:
@@ -99,62 +90,3 @@ def pull(self, from_path: AnyPath, to_path: LocalPath) -> LocalPath:
9990
]
10091
self._host_platform.sh_stdout(*scp_cmd)
10192
return to_path
102-
103-
def port_forward(self, local_port: int, remote_port: int) -> int:
104-
local_port, remote_port = self._validate_forwarding_ports(
105-
local_port, remote_port)
106-
self._port_forward_popens[local_port] = self.host_platform.popen(
107-
*self._build_ssh_cmd("-NL", f"{local_port}:localhost:{remote_port}"))
108-
self.host_platform.wait_for_port(local_port, self.PORT_FORWARDING_TIMEOUT)
109-
logging.debug("Forwarded Remote Port: %s:%s <= %s:%s", self._host_platform,
110-
local_port, self, remote_port)
111-
return local_port
112-
113-
def _validate_forwarding_ports(self, local_port: int,
114-
remote_port: int) -> tuple[int, int]:
115-
local_port = parse.NumberParser.positive_zero_int(local_port, "local_port")
116-
remote_port = parse.NumberParser.port_number(remote_port, "remote_port")
117-
if not local_port:
118-
local_port = self.host_platform.get_free_port()
119-
if local_port in self._port_forward_popens:
120-
raise RuntimeError(f"Cannot forward local port {local_port} twice.")
121-
return local_port, remote_port
122-
123-
def stop_port_forward(self, local_port: int) -> None:
124-
self._port_forward_popens.pop(local_port).terminate()
125-
126-
def reverse_port_forward(self, remote_port: int, local_port: int) -> int:
127-
# TODO: this should likely match with adb, where we support 0
128-
# for auto-allocating a remote_port
129-
remote_port, local_port = self._validate_reverse_forwarding_ports(
130-
remote_port, local_port)
131-
self._reverse_port_forward_popens[remote_port] = self.host_platform.popen(
132-
*self._build_ssh_cmd("-NR", f"{remote_port}:localhost:{local_port}"))
133-
self.wait_for_port(remote_port, self.PORT_FORWARDING_TIMEOUT)
134-
logging.debug("Forwarded Local Port: %s:%s => %s:%s", self._host_platform,
135-
local_port, self, remote_port)
136-
return remote_port
137-
138-
def _validate_reverse_forwarding_ports(self, remote_port: int,
139-
local_port: int) -> tuple[int, int]:
140-
remote_port = parse.NumberParser.port_number(remote_port, "remote_port")
141-
local_port = parse.NumberParser.positive_zero_int(local_port, "local_port")
142-
if not local_port:
143-
local_port = self.host_platform.get_free_port()
144-
if remote_port in self._reverse_port_forward_popens:
145-
raise RuntimeError(f"Cannot forward remote port {remote_port} twice.")
146-
return remote_port, local_port
147-
148-
def stop_reverse_port_forward(self, remote_port: int) -> None:
149-
self._reverse_port_forward_popens.pop(remote_port).terminate()
150-
151-
def _stop_all_port_forward(self) -> None:
152-
for port in list(self._port_forward_popens.keys()):
153-
self.stop_port_forward(port)
154-
for port in list(self._reverse_port_forward_popens.keys()):
155-
self.stop_reverse_port_forward(port)
156-
157-
assert not self._port_forward_popens, (
158-
"Did not stop all port forwarding processes.")
159-
assert not self._reverse_port_forward_popens, (
160-
"Did not stop all reverse port forwarding processes.")

0 commit comments

Comments
 (0)