Skip to content

Commit e0441b2

Browse files
authored
Better default support for Fluent Docker containers on Windows (#1785)
* better support for docker containers on Windows * fixing tests and improving examples.downloads * docstring and default behavior improvement * reworking data_transfer and container module const * fixing and revising previously failing tests * small adjustments * fix for #1798 and additional improvements * skipping failing test, see #1799 * only pseudo-tty for v241 * revising tests and skipping test_transcript, see #1802
1 parent 935f305 commit e0441b2

11 files changed

+284
-115
lines changed
Lines changed: 76 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,4 @@
1-
"""Functions to download sample datasets from the PyAnsys data repository.
2-
3-
Examples
4-
--------
5-
6-
>>> from ansys.fluent.core import examples
7-
>>> filename = examples.download_file("bracket.iges", "geometry")
8-
>>> filename
9-
'/home/user/.local/share/ansys_fluent_core/examples/bracket.iges'
10-
"""
1+
"""Functions to download sample datasets from the Ansys example data repository."""
112
import os
123
import re
134
import shutil
@@ -18,20 +9,25 @@
189
import ansys.fluent.core as pyfluent
1910

2011

21-
def delete_downloads() -> bool:
22-
"""Delete all downloaded examples to free space or update the files."""
12+
def delete_downloads():
13+
"""Delete all downloaded examples from the default examples folder to free space or update the files.
14+
15+
Notes
16+
-----
17+
The default examples path is given by ``pyfluent.EXAMPLES_PATH``."""
2318
shutil.rmtree(pyfluent.EXAMPLES_PATH)
2419
os.makedirs(pyfluent.EXAMPLES_PATH)
25-
return True
2620

2721

2822
def _decompress(filename: str) -> None:
23+
"""Decompress zipped file."""
2924
zip_ref = zipfile.ZipFile(filename, "r")
3025
zip_ref.extractall(pyfluent.EXAMPLES_PATH)
3126
return zip_ref.close()
3227

3328

3429
def _get_file_url(filename: str, directory: Optional[str] = None) -> str:
30+
"""Get file URL."""
3531
if directory:
3632
return (
3733
"https://github.com/ansys/example-data/raw/master/"
@@ -40,18 +36,29 @@ def _get_file_url(filename: str, directory: Optional[str] = None) -> str:
4036
return f"https://github.com/ansys/example-data/raw/master/{filename}"
4137

4238

43-
def _retrieve_file(url: str, filename: str, save_path: Optional[str] = None) -> str:
39+
def _retrieve_file(
40+
url: str,
41+
filename: str,
42+
save_path: Optional[str] = None,
43+
return_only_filename: Optional[bool] = False,
44+
) -> str:
45+
"""Download specified file from specified URL."""
46+
filename = os.path.basename(filename)
4447
if save_path is None:
4548
save_path = pyfluent.EXAMPLES_PATH
4649
else:
4750
save_path = os.path.abspath(save_path)
48-
local_path = os.path.join(save_path, os.path.basename(filename))
51+
local_path = os.path.join(save_path, filename)
4952
local_path_no_zip = re.sub(".zip$", "", local_path)
53+
filename_no_zip = re.sub(".zip$", "", filename)
5054
# First check if file has already been downloaded
5155
print("Checking if specified file already exists...")
5256
if os.path.isfile(local_path_no_zip) or os.path.isdir(local_path_no_zip):
5357
print(f"File already exists. File path:\n{local_path_no_zip}")
54-
return local_path_no_zip
58+
if return_only_filename:
59+
return filename_no_zip
60+
else:
61+
return local_path_no_zip
5562

5663
print("File does not exist. Downloading specified file...")
5764

@@ -63,16 +70,64 @@ def _retrieve_file(url: str, filename: str, save_path: Optional[str] = None) ->
6370
urlretrieve = urllib.request.urlretrieve
6471

6572
# Perform download
66-
saved_file, _ = urlretrieve(url, filename=local_path)
73+
urlretrieve(url, filename=local_path)
6774
if local_path.endswith(".zip"):
6875
_decompress(local_path)
6976
local_path = local_path_no_zip
77+
filename = filename_no_zip
7078
print(f"Download successful. File path:\n{local_path}")
71-
return local_path
79+
if return_only_filename:
80+
return filename
81+
else:
82+
return local_path
7283

7384

7485
def download_file(
75-
filename: str, directory: Optional[str] = None, save_path: Optional[str] = None
76-
):
86+
filename: str,
87+
directory: Optional[str] = None,
88+
save_path: Optional[str] = None,
89+
return_only_filename: Optional[bool] = None,
90+
) -> str:
91+
"""Download specified example file from the Ansys example data repository.
92+
93+
Parameters
94+
----------
95+
filename : str
96+
File to download.
97+
directory : str, optional
98+
Ansys example data repository directory where specified file is located. If not specified, looks for the file
99+
in the root directory of the repository.
100+
save_path : str, optional
101+
Path to download the specified file to.
102+
return_only_filename : bool, optional
103+
When unspecified, defaults to False, unless the PYFLUENT_LAUNCH_CONTAINER=1 environment variable is specified,
104+
in which case defaults to True.
105+
Relevant when using Fluent Docker container images, as the full path for the imported file from
106+
the host side is not necessarily going to be the same as the one for Fluent inside the container.
107+
Assuming the Fluent inside the container has its working directory set to the path that was mounted from
108+
the host, and that the example files are being downloaded by the host to this same path, only the filename is
109+
required for Fluent to find and open the file.
110+
111+
Returns
112+
-------
113+
str
114+
Filepath of the downloaded or already existing file, or only the file name if ``return_only_filename=True``.
115+
116+
Examples
117+
--------
118+
>>> from ansys.fluent.core import examples
119+
>>> filepath = examples.download_file("bracket.iges", "geometry")
120+
>>> filepath
121+
'/home/user/.local/share/ansys_fluent_core/examples/bracket.iges'
122+
>>> filename = examples.download_file("bracket.iges", "geometry", return_only_filename=True)
123+
>>> filename
124+
'bracket.iges'
125+
"""
126+
if return_only_filename is None:
127+
if os.getenv("PYFLUENT_LAUNCH_CONTAINER") == "1":
128+
return_only_filename = True
129+
else:
130+
return_only_filename = False
131+
77132
url = _get_file_url(filename, directory)
78-
return _retrieve_file(url, filename, save_path)
133+
return _retrieve_file(url, filename, save_path, return_only_filename)

src/ansys/fluent/core/fluent_connection.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -255,8 +255,8 @@ def __init__(
255255
logger.debug("Cortex connection properties successfully obtained.")
256256
except _InactiveRpcError:
257257
logger.warning(
258-
"Cortex properties unobtainable, force exit "
259-
" methods are not going to work, proceeding..."
258+
"Fluent Cortex properties unobtainable, force exit and other"
259+
"methods are not going to work properly, proceeding..."
260260
)
261261
cortex_host = None
262262
cortex_pid = None

src/ansys/fluent/core/launcher/fluent_container.py

Lines changed: 43 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -28,27 +28,26 @@
2828
2929
>>> import ansys.fluent.core as pyfluent
3030
>>> config_dict = pyfluent.launch_fluent(start_container=True, dry_run=True)
31-
Container run configuration information:
32-
image_name = 'ghcr.io/ansys/pyfluent:v23.1.0'
33-
>>> config_dict
31+
Docker container run configuration information:
32+
33+
config_dict =
3434
{'auto_remove': True,
35-
'command': ['-gu',
36-
'-sifile=/home/user/.local/share/ansys_fluent_core/examples/serverinfo-reh96tuo.txt',
37-
'3ddp'],
35+
'command': ['-gu', '-sifile=/tmpdir/serverinfo-lpqsdldw.txt', '3ddp'],
3836
'detach': True,
39-
'environment': {'ANSYSLMD_LICENSE_FILE': '1450@license_server.com',
40-
'REMOTING_PORTS': '57193/portspan=2'},
37+
'environment': {'ANSYSLMD_LICENSE_FILE': '[email protected]',
38+
'REMOTING_PORTS': '54000/portspan=2'},
39+
'fluent_image': 'ghcr.io/ansys/pyfluent:v23.2.0',
4140
'labels': {'test_name': 'none'},
42-
'ports': {'57193': 57193},
43-
'volumes': ['/home/user/.local/share/ansys_fluent_core/examples:/home/user/.local/share/ansys_fluent_core/examples'],
44-
'working_dir': '/home/user/.local/share/ansys_fluent_core/examples'}
41+
'ports': {'54000': 54000},
42+
'volumes': ['/home/user/.local/share/ansys_fluent_core/examples:/tmpdir'],
43+
'working_dir': '/tmpdir'}
4544
>>> config_dict.update(image_name='custom_fluent', image_tag='v23.1.0', mem_limit='1g')
4645
>>> session = pyfluent.launch_fluent(container_dict=config_dict)
4746
4847
"""
4948
import logging
5049
import os
51-
from pathlib import Path
50+
from pathlib import Path, PurePosixPath
5251
import tempfile
5352
from typing import List, Union
5453

@@ -59,13 +58,14 @@
5958
import docker
6059

6160
logger = logging.getLogger("pyfluent.launcher")
61+
DEFAULT_CONTAINER_MOUNT_PATH = "/tmpdir"
6262

6363

6464
def configure_container_dict(
6565
args: List[str],
6666
host_mount_path: Union[str, Path] = None,
6767
container_mount_path: Union[str, Path] = None,
68-
timeout: int = 30,
68+
timeout: int = 60,
6969
port: int = None,
7070
license_server: str = None,
7171
container_server_info_file: Union[str, Path] = None,
@@ -74,7 +74,7 @@ def configure_container_dict(
7474
image_name: str = None,
7575
image_tag: str = None,
7676
**container_dict,
77-
) -> (str, dict, int, int, Path, bool):
77+
) -> (dict, int, int, Path, bool):
7878
"""Parses the parameters listed below, and sets up the container configuration file.
7979
8080
Parameters
@@ -112,7 +112,7 @@ def configure_container_dict(
112112
container_dict : dict
113113
timeout : int
114114
port : int
115-
container_server_info_file : Path
115+
host_server_info_file : Path
116116
remove_server_info_file: bool
117117
118118
Notes
@@ -133,7 +133,7 @@ def configure_container_dict(
133133

134134
if not container_mount_path:
135135
container_mount_path = os.getenv(
136-
"PYFLUENT_CONTAINER_MOUNT_PATH", host_mount_path
136+
"PYFLUENT_CONTAINER_MOUNT_PATH", DEFAULT_CONTAINER_MOUNT_PATH
137137
)
138138

139139
if "volumes" not in container_dict:
@@ -183,21 +183,26 @@ def configure_container_dict(
183183
"Specified a server info file command argument as well as "
184184
"a container_server_info_file, pick one."
185185
)
186-
container_server_info_file = Path(v.lstrip("-sifile=")).name
186+
container_server_info_file = PurePosixPath(
187+
v.replace("-sifile=", "")
188+
).name
187189
logger.debug(
188190
f"Found server info file specification for {container_server_info_file}."
189191
)
190192

191193
if container_server_info_file:
192194
container_server_info_file = (
193-
Path(container_mount_path) / Path(container_server_info_file).name
195+
PurePosixPath(container_mount_path)
196+
/ PurePosixPath(container_server_info_file).name
194197
)
195198
else:
196199
fd, sifile = tempfile.mkstemp(
197200
suffix=".txt", prefix="serverinfo-", dir=host_mount_path
198201
)
199202
os.close(fd)
200-
container_server_info_file = Path(container_mount_path) / Path(sifile).name
203+
container_server_info_file = (
204+
PurePosixPath(container_mount_path) / Path(sifile).name
205+
)
201206

202207
if not fluent_image:
203208
if not image_tag:
@@ -210,9 +215,11 @@ def configure_container_dict(
210215
fluent_image = f"{image_name}:{image_tag}"
211216
else:
212217
raise ValueError(
213-
"Missing 'fluent_image' specification for Docker container launch."
218+
"Missing 'fluent_image', or 'image_tag' and 'image_name', specification for Docker container launch."
214219
)
215220

221+
container_dict["fluent_image"] = fluent_image
222+
216223
fluent_commands = ["-gu", f"-sifile={container_server_info_file}"] + args
217224

218225
container_dict_default = {}
@@ -222,18 +229,22 @@ def configure_container_dict(
222229
auto_remove=True,
223230
)
224231

232+
if fluent_image.split(":")[1] == "v24.1.0":
233+
container_dict_default.update(tty=True)
234+
225235
for k, v in container_dict_default.items():
226236
if k not in container_dict:
227237
container_dict[k] = v
228238

229239
logger.debug(f"container_dict after processing: {container_dict}")
230240

241+
host_server_info_file = Path(host_mount_path) / container_server_info_file.name
242+
231243
return (
232-
fluent_image,
233244
container_dict,
234245
timeout,
235246
port,
236-
container_server_info_file,
247+
host_server_info_file,
237248
remove_server_info_file,
238249
)
239250

@@ -268,39 +279,38 @@ def start_fluent_container(args: List[str], container_dict: dict = None) -> (int
268279
logger.debug(f"container_vars:{container_vars}")
269280

270281
(
271-
fluent_image,
272282
config_dict,
273283
timeout,
274284
port,
275-
container_server_info_file,
285+
host_server_info_file,
276286
remove_server_info_file,
277287
) = container_vars
278288

279289
try:
280-
if not container_server_info_file.exists():
281-
container_server_info_file.mkdir(exist_ok=True)
290+
if not host_server_info_file.exists():
291+
host_server_info_file.parents[0].mkdir(exist_ok=True)
282292

283-
container_server_info_file.touch(exist_ok=True)
284-
last_mtime = container_server_info_file.stat().st_mtime
293+
host_server_info_file.touch(exist_ok=True)
294+
last_mtime = host_server_info_file.stat().st_mtime
285295

286296
docker_client = docker.from_env()
287297

288298
logger.debug("Starting Fluent docker container...")
289299

290-
docker_client.containers.run(fluent_image, **config_dict)
300+
docker_client.containers.run(config_dict.pop("fluent_image"), **config_dict)
291301

292302
success = timeout_loop(
293-
lambda: container_server_info_file.stat().st_mtime > last_mtime, timeout
303+
lambda: host_server_info_file.stat().st_mtime > last_mtime, timeout
294304
)
295305

296306
if not success:
297307
raise RuntimeError(
298308
"Fluent container launch timeout, will have to stop container manually."
299309
)
300310
else:
301-
_, _, password = _parse_server_info_file(str(container_server_info_file))
311+
_, _, password = _parse_server_info_file(str(host_server_info_file))
302312

303313
return port, password
304314
finally:
305-
if remove_server_info_file and container_server_info_file.exists():
306-
container_server_info_file.unlink()
315+
if remove_server_info_file and host_server_info_file.exists():
316+
host_server_info_file.unlink()

src/ansys/fluent/core/launcher/launcher.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -760,11 +760,12 @@ def launch_fluent(
760760
args.append(" -meshing")
761761

762762
if dry_run:
763-
image_name, config_dict, *_ = configure_container_dict(args, container_dict)
763+
if container_dict is None:
764+
container_dict = {}
765+
config_dict, *_ = configure_container_dict(args, **container_dict)
764766
from pprint import pprint
765767

766-
print("\nContainer run configuration information:\n")
767-
print(f"image_name = '{image_name}'\n")
768+
print("\nDocker container run configuration:\n")
768769
print("config_dict = ")
769770
pprint(config_dict)
770771
return config_dict

0 commit comments

Comments
 (0)