From 2b9d9d2334599b886718c24a72371f1522d0ceb9 Mon Sep 17 00:00:00 2001 From: Gustavo Martins Date: Fri, 22 Dec 2023 14:58:55 -0300 Subject: [PATCH 1/2] Block launch rocky server multiple times ROCKY-20938 --- src/ansys/rocky/core/client.py | 4 +++- src/ansys/rocky/core/launcher.py | 23 +++++++++++++++++++---- tests/test_pyrocky.py | 17 +++++++++++++++++ 3 files changed, 39 insertions(+), 5 deletions(-) diff --git a/src/ansys/rocky/core/client.py b/src/ansys/rocky/core/client.py index bfc1d97f..9b09aabc 100644 --- a/src/ansys/rocky/core/client.py +++ b/src/ansys/rocky/core/client.py @@ -28,10 +28,12 @@ from ansys.rocky.core.exceptions import RockyApiError +ROCKY_SERVER_PORT = 50615 + _ROCKY_API = None -def connect_to_rocky(host: str = "localhost", port: int = 50615) -> "RockyClient": +def connect_to_rocky(host: str = "localhost", port: int = ROCKY_SERVER_PORT) -> "RockyClient": """Connect to a Rocky Application instance. Parameters diff --git a/src/ansys/rocky/core/launcher.py b/src/ansys/rocky/core/launcher.py index 6f305b49..d4bce775 100644 --- a/src/ansys/rocky/core/launcher.py +++ b/src/ansys/rocky/core/launcher.py @@ -25,16 +25,18 @@ from pathlib import Path import subprocess import time -from typing import Optional +from typing import Optional, Union from Pyro5.errors import CommunicationError -from ansys.rocky.core.client import RockyClient, connect_to_rocky +from ansys.rocky.core.client import ROCKY_SERVER_PORT, RockyClient, connect_to_rocky from ansys.rocky.core.exceptions import RockyLaunchError +_WAIT_ROCKY_START = 60 + def launch_rocky( - rocky_exe: Optional[Path] = None, + rocky_exe: Optional[Union[Path, str]] = None, headless: bool = True, ) -> RockyClient: """ @@ -54,6 +56,12 @@ def launch_rocky( RockyClient A `RockyClient` instance connected to the launched Rocky application. """ + if isinstance(rocky_exe, str): + rocky_exe = Path(rocky_exe) + + if _is_port_busy(ROCKY_SERVER_PORT): + raise RockyLaunchError(f"Port {ROCKY_SERVER_PORT} already in use") + if rocky_exe is None: for awp_root in ["AWP_ROOT241", "AWP_ROOT232"]: if awp_root not in os.environ: @@ -96,4 +104,11 @@ def launch_rocky( return client -_WAIT_ROCKY_START = 60 +def _is_port_busy(port: int) -> bool: + """ + Check if there is already a Rocky server running. + """ + import socket + + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + return s.connect_ex(("localhost", port)) == 0 diff --git a/tests/test_pyrocky.py b/tests/test_pyrocky.py index c05dce44..d1f2d8a4 100644 --- a/tests/test_pyrocky.py +++ b/tests/test_pyrocky.py @@ -23,6 +23,8 @@ import pytest import ansys.rocky.core as pyrocky +from ansys.rocky.core.client import ROCKY_SERVER_PORT +from ansys.rocky.core.launcher import RockyLaunchError @pytest.fixture() @@ -81,3 +83,18 @@ def test_sequences_interface(rocky_session): # Test __del__ del inlets_outlets[0] assert {e.GetName() for e in inlets_outlets} == {"Inlet2"} + + +def test_pyrocky_launch_multiple_servers(): + """ + Test that start multiple rocky servers is not allowed. + """ + import socket + + # Emulating Rocky server already running by binding socket to the server address. + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.bind(("localhost", ROCKY_SERVER_PORT)) + s.listen(10) + + with pytest.raises(RockyLaunchError, match="Port \d+ already in use"): + pyrocky.launch_rocky() From 8940550df513642136d711ea435b056b19496632 Mon Sep 17 00:00:00 2001 From: "Igor T. Ghisi" Date: Tue, 23 Jan 2024 18:24:10 -0300 Subject: [PATCH 2/2] Allow to set Rocky server port on launch --- src/ansys/rocky/core/client.py | 11 +++++++---- src/ansys/rocky/core/launcher.py | 29 ++++++++++++++++++++++------- tests/test_pyrocky.py | 6 +++--- 3 files changed, 32 insertions(+), 14 deletions(-) diff --git a/src/ansys/rocky/core/client.py b/src/ansys/rocky/core/client.py index 9b09aabc..d9ed1788 100644 --- a/src/ansys/rocky/core/client.py +++ b/src/ansys/rocky/core/client.py @@ -21,20 +21,23 @@ # SOFTWARE. import pickle -from typing import Generator +from typing import Final, Generator import Pyro5.api import serpent from ansys.rocky.core.exceptions import RockyApiError -ROCKY_SERVER_PORT = 50615 +DEFAULT_SERVER_PORT: Final[int] = 50615 _ROCKY_API = None -def connect_to_rocky(host: str = "localhost", port: int = ROCKY_SERVER_PORT) -> "RockyClient": - """Connect to a Rocky Application instance. +def connect_to_rocky( + host: str = "localhost", port: int = DEFAULT_SERVER_PORT +) -> "RockyClient": + """ + Connect to a Rocky Application instance. Parameters ---------- diff --git a/src/ansys/rocky/core/launcher.py b/src/ansys/rocky/core/launcher.py index d4bce775..72fb602d 100644 --- a/src/ansys/rocky/core/launcher.py +++ b/src/ansys/rocky/core/launcher.py @@ -29,15 +29,17 @@ from Pyro5.errors import CommunicationError -from ansys.rocky.core.client import ROCKY_SERVER_PORT, RockyClient, connect_to_rocky +from ansys.rocky.core.client import DEFAULT_SERVER_PORT, RockyClient, connect_to_rocky from ansys.rocky.core.exceptions import RockyLaunchError -_WAIT_ROCKY_START = 60 +_CONNECT_TO_SERVER_TIMEOUT = 60 def launch_rocky( rocky_exe: Optional[Union[Path, str]] = None, + *, headless: bool = True, + server_port: int = DEFAULT_SERVER_PORT, ) -> RockyClient: """ Launch Rocky executable with PyRocky server enabled, wait Rocky to start up and @@ -50,6 +52,8 @@ def launch_rocky( environment variables `AWP_ROOT241` and `AWP_ROOT232`. headless : bool, optional Whether to launch Rocky in headless mode. Default is `True`. + server_port: int, optional + Set the port used to host Rocky PyRocky server. Returns ------- @@ -59,8 +63,8 @@ def launch_rocky( if isinstance(rocky_exe, str): rocky_exe = Path(rocky_exe) - if _is_port_busy(ROCKY_SERVER_PORT): - raise RockyLaunchError(f"Port {ROCKY_SERVER_PORT} already in use") + if _is_port_busy(server_port): + raise RockyLaunchError(f"Port {server_port} already in use") if rocky_exe is None: for awp_root in ["AWP_ROOT241", "AWP_ROOT232"]: @@ -76,7 +80,7 @@ def launch_rocky( if not rocky_exe.is_file(): raise FileNotFoundError(f"Rocky executable not found at {rocky_exe}") - cmd = [rocky_exe, "--pyrocky"] + cmd = [rocky_exe, "--pyrocky", "--pyrocky-port", str(server_port)] if headless: cmd.append("--headless") with contextlib.suppress(subprocess.TimeoutExpired): @@ -87,10 +91,11 @@ def launch_rocky( if rocky_process.returncode is not None: raise RockyLaunchError(f"Error launching Rocky:\n {' '.join(cmd)}") - client = connect_to_rocky() + client = connect_to_rocky(port=server_port) # TODO: A more elegant way to find out that Rocky Pyro server started. - for _ in range(_WAIT_ROCKY_START): + now = time.time() + while (time.time() - now) < _CONNECT_TO_SERVER_TIMEOUT: try: client.api.GetProject() except CommunicationError: @@ -107,6 +112,16 @@ def launch_rocky( def _is_port_busy(port: int) -> bool: """ Check if there is already a Rocky server running. + + Parameters + ---------- + port : int + The port to be checked. + + Returns + ------- + bool + Whether the port is busy or not. """ import socket diff --git a/tests/test_pyrocky.py b/tests/test_pyrocky.py index d1f2d8a4..b1ea3a3c 100644 --- a/tests/test_pyrocky.py +++ b/tests/test_pyrocky.py @@ -23,7 +23,7 @@ import pytest import ansys.rocky.core as pyrocky -from ansys.rocky.core.client import ROCKY_SERVER_PORT +from ansys.rocky.core.client import DEFAULT_SERVER_PORT from ansys.rocky.core.launcher import RockyLaunchError @@ -93,8 +93,8 @@ def test_pyrocky_launch_multiple_servers(): # Emulating Rocky server already running by binding socket to the server address. with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - s.bind(("localhost", ROCKY_SERVER_PORT)) + s.bind(("localhost", DEFAULT_SERVER_PORT)) s.listen(10) - with pytest.raises(RockyLaunchError, match="Port \d+ already in use"): + with pytest.raises(RockyLaunchError, match=r"Port \d+ already in use"): pyrocky.launch_rocky()