diff --git a/doc/changelog.d/2419.added.md b/doc/changelog.d/2419.added.md new file mode 100644 index 0000000000..5e51b8b118 --- /dev/null +++ b/doc/changelog.d/2419.added.md @@ -0,0 +1 @@ +Enhancements to transport mode diff --git a/src/ansys/geometry/core/connection/client.py b/src/ansys/geometry/core/connection/client.py index 38c250b316..4af71f2ea2 100644 --- a/src/ansys/geometry/core/connection/client.py +++ b/src/ansys/geometry/core/connection/client.py @@ -23,11 +23,9 @@ import atexit import logging -import os from pathlib import Path import time from typing import Optional -import warnings from beartype import beartype as check_input_types import grpc @@ -52,7 +50,7 @@ def _create_geometry_channel( target: str, - transport_mode: str | None = None, + transport_mode: str, uds_dir: Path | str | None = None, uds_id: str | None = None, certs_dir: Path | str | None = None, @@ -64,9 +62,8 @@ def _create_geometry_channel( target : str Target of the channel. This is usually a string in the form of ``host:port``. - transport_mode : str | None - Transport mode selected, by default `None` and thus it will be selected - for you based on the connection criteria. Options are: "insecure", "uds", "wnua", "mtls" + transport_mode : str + Transport mode selected. Options are: "insecure", "uds", "wnua", "mtls" uds_dir : Path | str | None Directory to use for Unix Domain Sockets (UDS) transport mode. By default `None` and thus it will use the "~/.conn" folder. @@ -102,9 +99,9 @@ def _create_geometry_channel( # Create the channel accordingly return create_channel( + transport_mode=transport_mode, host=host, port=port, - transport_mode=transport_mode, uds_service="aposdas_socket", uds_dir=uds_dir, uds_id=uds_id, @@ -139,8 +136,8 @@ def wait_until_healthy( * If the total elapsed time exceeds the value for the ``timeout`` parameter, a ``TimeoutError`` is raised. transport_mode : str | None - Transport mode selected, by default `None` and thus it will be selected - for you based on the connection criteria. Options are: "insecure", "uds", "wnua", "mtls" + Transport mode selected. Needed if channel is a string. + Options are: "insecure", "uds", "wnua", "mtls". uds_dir : Path | str | None Directory to use for Unix Domain Sockets (UDS) transport mode. By default `None` and thus it will use the "~/.conn" folder. @@ -168,23 +165,22 @@ def wait_until_healthy( t_max = time.time() + timeout t_out = 0.1 - # If transport mode is not specified, default to insecure when running in CI - if transport_mode is None: - if os.getenv("IS_WORKFLOW_RUNNING") is not None: - warnings.warn( - "Transport mode forced to 'insecure' when running in CI workflows.", - ) - transport_mode = "insecure" - else: + # If the channel is a string, create a channel using the specified transport mode + channel_creation_required = True if isinstance(channel, str) else False + tmp_channel = None + + # If transport mode is not specified and a channel creation is required, raise an error + if channel_creation_required: + if transport_mode is None: raise ValueError( - "Transport mode must be specified when not running in CI workflows." + "Transport mode must be specified." " Use 'transport_mode' parameter with one of the possible options." " Options are: 'insecure', 'uds', 'wnua', 'mtls'." ) + else: + from ansys.tools.common.cyberchannel import verify_transport_mode - # If the channel is a string, create a channel using the default insecure channel - channel_creation_required = True if isinstance(channel, str) else False - tmp_channel = None + verify_transport_mode(transport_mode) while time.time() < t_max: try: @@ -262,8 +258,8 @@ class GrpcClient: Protocol version to use for communication with the server. If None, v0 is used. Available versions are "v0", "v1", etc. transport_mode : str | None - Transport mode selected, by default `None` and thus it will be selected - for you based on the connection criteria. Options are: "insecure", "uds", "wnua", "mtls" + Transport mode selected. Needed if ``channel`` is not provided. + Options are: "insecure", "uds", "wnua", "mtls". uds_dir : Path | str | None Directory to use for Unix Domain Sockets (UDS) transport mode. By default `None` and thus it will use the "~/.conn" folder. diff --git a/src/ansys/geometry/core/connection/docker_instance.py b/src/ansys/geometry/core/connection/docker_instance.py index b0d9fb8ebb..054cd9c75c 100644 --- a/src/ansys/geometry/core/connection/docker_instance.py +++ b/src/ansys/geometry/core/connection/docker_instance.py @@ -182,6 +182,7 @@ def __init__( # Initialize instance variables self._container: Container = None self._existed_previously: bool = False + self._transport_mode: str | None = transport_mode # Check the port availability port_available, cont = self._check_port_availability(port) @@ -197,6 +198,17 @@ def __init__( # Finally, store the container self._container = cont self._existed_previously = True + # If no transport mode was provided, try to extract it from the existing + # container command line arguments + if transport_mode is None: + cmd_args = cont.attrs["Config"].get("Cmd", []) + for arg in cmd_args: + if "--transport-mode=" in arg: + self._transport_mode = arg.split("=")[1] + break + # If still None, default to "mtls" + if self._transport_mode is None: + self._transport_mode = "mtls" return # At this stage, confirm that you have to deploy our own Geometry service. @@ -389,6 +401,7 @@ def _deploy_container( # If the deployment went fine, this means that you have deployed the service. self._container = container self._existed_previously = False + self._transport_mode = transport_mode @property def container(self) -> "Container": @@ -404,6 +417,18 @@ def existed_previously(self) -> bool: """ return self._existed_previously + @property + def transport_mode(self) -> str: + """Transport mode used by the Docker container. + + Returns + ------- + str + Transport mode used by the Docker container. If no transport mode + was specified during initialization, it defaults to "mtls". + """ + return self._transport_mode if self._transport_mode else "mtls" + def get_geometry_container_type(instance: LocalDockerInstance) -> GeometryContainers | None: """Provide back the ``GeometryContainers`` value. diff --git a/src/ansys/geometry/core/connection/launcher.py b/src/ansys/geometry/core/connection/launcher.py index 2fccd3a5e0..95242fd4ba 100644 --- a/src/ansys/geometry/core/connection/launcher.py +++ b/src/ansys/geometry/core/connection/launcher.py @@ -25,7 +25,6 @@ import os from pathlib import Path from typing import TYPE_CHECKING -import warnings from ansys.geometry.core.connection.backend import ApiVersions, BackendType import ansys.geometry.core.connection.defaults as pygeom_defaults @@ -340,7 +339,7 @@ def launch_docker_modeler( in which case the client logs to the console. transport_mode : str | None Transport mode selected, by default `None` and thus it will be selected - for you based on the connection criteria. Options are: "insecure", "mtls" + for you based on the connection criteria. Options are: "insecure", "mtls". certs_dir : Path | str | None Directory to use for TLS certificates. By default `None` and thus search for the "ANSYS_GRPC_CERTIFICATES" environment variable. @@ -360,12 +359,6 @@ def launch_docker_modeler( if not _HAS_DOCKER: # pragma: no cover raise ModuleNotFoundError("The package 'docker' is required to use this function.") - if os.getenv("IS_WORKFLOW_RUNNING") is not None: - warnings.warn( - "Transport mode forced to 'insecure' when running in CI workflows.", - ) - transport_mode = "insecure" - # Call the LocalDockerInstance ctor. docker_instance = LocalDockerInstance( port=port, @@ -384,7 +377,7 @@ def launch_docker_modeler( docker_instance=docker_instance, logging_level=client_log_level, logging_file=client_log_file, - transport_mode=transport_mode if transport_mode else "mtls", + transport_mode=docker_instance.transport_mode, certs_dir=certs_dir, ) diff --git a/src/ansys/geometry/core/connection/validate.py b/src/ansys/geometry/core/connection/validate.py index 9c592e9154..9e821670c0 100644 --- a/src/ansys/geometry/core/connection/validate.py +++ b/src/ansys/geometry/core/connection/validate.py @@ -36,6 +36,19 @@ def validate(*args, **kwargs): # pragma: no cover """Create a client using the default settings and validate it.""" - print(GrpcClient(*args, **kwargs)) + # Assume local transport mode for validation if not provided + if "transport_mode" not in kwargs: + import platform + + kwargs["transport_mode"] = "wnua" if platform.system() == "Windows" else "uds" + try: + GrpcClient(*args, **kwargs) + except Exception: + # Let's give it a try to insecure mode... just in case + kwargs["transport_mode"] = "insecure" + GrpcClient(*args, **kwargs) + else: + GrpcClient(*args, **kwargs) + # TODO: consider adding additional server stat reporting # https://github.com/ansys/pyansys-geometry/issues/1319 diff --git a/src/ansys/geometry/core/modeler.py b/src/ansys/geometry/core/modeler.py index aa6847c209..86db4603bb 100644 --- a/src/ansys/geometry/core/modeler.py +++ b/src/ansys/geometry/core/modeler.py @@ -87,8 +87,8 @@ class Modeler: Protocol version to use for communication with the server. If None, v0 is used. Available versions are "v0", "v1", etc. transport_mode : str | None - Transport mode selected, by default `None` and thus it will be selected - for you based on the connection criteria. Options are: "insecure", "uds", "wnua", "mtls" + Transport mode selected. Needed if ``channel`` is not provided. + Options are: "insecure", "uds", "wnua", "mtls". uds_dir : Path | str | None Directory to use for Unix Domain Sockets (UDS) transport mode. By default `None` and thus it will use the "~/.conn" folder. diff --git a/tests/conftest.py b/tests/conftest.py index 52453fc41d..f81ee8f2b7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -79,9 +79,9 @@ def pytest_addoption(parser): parser.addoption( "--transport-mode", action="store", - default="default", - help=("Specify the transport mode to use for the tests. By default, 'default'."), - choices=("default", "insecure", "uds", "wnua", "mtls"), + default="insecure", + help=("Specify the transport mode to use for the tests. By default, 'insecure'."), + choices=("insecure", "uds", "wnua", "mtls"), ) @@ -184,10 +184,9 @@ def proto_version(request): def transport_mode(request): """Fixture to determine transport mode to be used.""" - value: str = request.config.getoption("--transport-mode", default="default") - mode = None if value.lower() == "default" else value.lower() + value: str = request.config.getoption("--transport-mode", default="insecure") - return mode + return value.lower() @pytest.fixture diff --git a/tests/integration/test_client.py b/tests/integration/test_client.py index b99956c4a4..63982925a0 100644 --- a/tests/integration/test_client.py +++ b/tests/integration/test_client.py @@ -32,10 +32,10 @@ @pytest.fixture(scope="function") -def client(modeler: Modeler): +def client(modeler: Modeler, transport_mode: str) -> GrpcClient: # this uses DEFAULT_HOST and DEFAULT_PORT which are set by environment # variables in the workflow - return GrpcClient() + return GrpcClient(transport_mode=transport_mode) def test_client_init(client: GrpcClient): diff --git a/tests/integration/test_design.py b/tests/integration/test_design.py index 2842d184d9..53d17c3971 100644 --- a/tests/integration/test_design.py +++ b/tests/integration/test_design.py @@ -1312,7 +1312,7 @@ def test_upload_file(modeler: Modeler, tmp_path_factory: pytest.TempPathFactory) assert path_on_server is not None -def test_stream_upload_file(tmp_path_factory: pytest.TempPathFactory): +def test_stream_upload_file(tmp_path_factory: pytest.TempPathFactory, transport_mode: str): """Test uploading a file to the server.""" # Define a new maximum message length import ansys.geometry.core.connection.defaults as pygeom_defaults @@ -1333,7 +1333,7 @@ def test_stream_upload_file(tmp_path_factory: pytest.TempPathFactory): # Upload file - necessary to import the Modeler class and create an instance from ansys.geometry.core import Modeler - modeler = Modeler() + modeler = Modeler(transport_mode=transport_mode) path_on_server = modeler._upload_file_stream(file) assert path_on_server is not None finally: diff --git a/tests/integration/test_design_import.py b/tests/integration/test_design_import.py index 12f1fdb1c0..99b20af327 100644 --- a/tests/integration/test_design_import.py +++ b/tests/integration/test_design_import.py @@ -110,7 +110,7 @@ def _checker_method(comp: Component, comp_ref: Component, precise_check: bool = _checker_method(subcomp, subcomp_ref, precise_check) -def test_design_import_simple_case(modeler: Modeler): +def test_design_import_simple_case(modeler: Modeler, transport_mode: str): # With the given session let's create a the following Design # # Create your design on the server side @@ -157,7 +157,7 @@ def test_design_import_simple_case(modeler: Modeler): ) # Now, let's create a new client session - new_client = Modeler() + new_client = Modeler(transport_mode=transport_mode) read_design = new_client.read_existing_design() # And now assert all its elements diff --git a/tests/test_logging.py b/tests/test_logging.py index 31020a11c8..a25bceae4c 100644 --- a/tests/test_logging.py +++ b/tests/test_logging.py @@ -39,11 +39,11 @@ LOG_LEVELS = {"CRITICAL": 50, "ERROR": 40, "WARNING": 30, "INFO": 20, "DEBUG": 10} -def test_add_instance(): +def test_add_instance(transport_mode: str): """Testing adding an instance logger while checking if log has certain key""" base_name = "root" instance_logger_1 = LOG.add_instance_logger( - name=base_name, client_instance=GrpcClient(), level=10 + name=base_name, client_instance=GrpcClient(transport_mode=transport_mode), level=10 ) instance_logger_1.info("This is a message from the first instance logger.") with pytest.raises(KeyError, match="There is no instances with name root_4."):