diff --git a/src/DIRAC/Core/Utilities/Os.py b/src/DIRAC/Core/Utilities/Os.py index 0ac0c74826d..e00427d24f3 100755 --- a/src/DIRAC/Core/Utilities/Os.py +++ b/src/DIRAC/Core/Utilities/Os.py @@ -3,10 +3,11 @@ by default on Error they return None """ import os +import threading import DIRAC -from DIRAC.Core.Utilities.Subprocess import shellCall, systemCall from DIRAC.Core.Utilities import List +from DIRAC.Core.Utilities.Subprocess import shellCall, systemCall DEBUG = 0 @@ -128,3 +129,33 @@ def sourceEnv(timeout, cmdTuple, inputEnv=None): result["stderr"] = stderr return result + + +def safe_listdir(directory, timeout=60): + """This is a "safe" list directory, + for lazily-loaded File Systems like CVMFS. + There's by default a 60 seconds timeout. + + .. warning:: + There is no distinction between an empty directory, and a non existent one. + It will return `[]` in both cases. + + :param str directory: directory to list + :param int timeout: optional timeout, in seconds. Defaults to 60. + """ + + def listdir(directory): + try: + return os.listdir(directory) + except FileNotFoundError: + print(f"{directory} not found") + return [] + + contents = [] + t = threading.Thread(target=lambda: contents.extend(listdir(directory))) + t.daemon = True # don't delay program's exit + t.start() + t.join(timeout) + if t.is_alive(): + return None # timeout + return contents diff --git a/src/DIRAC/Core/scripts/dirac_apptainer_exec.py b/src/DIRAC/Core/scripts/dirac_apptainer_exec.py index a557c009c34..f6ab81efa7b 100644 --- a/src/DIRAC/Core/scripts/dirac_apptainer_exec.py +++ b/src/DIRAC/Core/scripts/dirac_apptainer_exec.py @@ -10,6 +10,7 @@ from DIRAC import S_ERROR, gConfig, gLogger from DIRAC.Core.Base.Script import Script from DIRAC.Core.Security.Locations import getCAsLocation, getProxyLocation, getVOMSLocation +from DIRAC.Core.Utilities.Os import safe_listdir from DIRAC.Core.Utilities.Subprocess import systemCall @@ -73,7 +74,7 @@ def main(): cmd.extend(["--contain"]) # use minimal /dev and empty other directories (e.g. /tmp and $HOME) cmd.extend(["--ipc"]) # run container in a new IPC namespace cmd.extend(["--pid"]) # run container in a new PID namespace - cmd.extend(["--bind", f"{os.getcwd()}:/mnt"]) # bind current directory for dirac_container.sh + cmd.extend(["--bind", f"{os.getcwd()}"]) # bind current directory for dirac_container.sh if proxy_location: cmd.extend(["--bind", f"{proxy_location}:/etc/proxy"]) # bind proxy file cmd.extend(["--bind", f"{getCAsLocation()}:/etc/grid-security/certificates"]) # X509_CERT_DIR @@ -83,12 +84,18 @@ def main(): cmd.extend(["--bind", f"{vomses_location}:/etc/grid-security/vomses"]) # X509_VOMSES cmd.extend(["--bind", "{0}:{0}:ro".format(etc_dir)]) # etc dir for dirac.cfg cmd.extend(["--bind", "{0}:{0}:ro".format(os.path.join(os.path.realpath(sys.base_prefix)))]) # code dir - cmd.extend(["--cwd", "/mnt"]) # set working directory to /mnt + # here bind optional paths + for bind_path in gConfig.getValue("/Resources/Computing/Singularity/BindPaths", []): + if safe_listdir(bind_path): + cmd.extend(["--bind", f"{bind_path}:{bind_path}"]) + else: + gLogger.warning(f"Bind path {bind_path} does not exist, skipping") + cmd.extend(["--cwd", f"{os.getcwd()}"]) # set working directory rootImage = user_image or gConfig.getValue("/Resources/Computing/Singularity/ContainerRoot") or CONTAINER_DEFROOT if os.path.isdir(rootImage) or os.path.isfile(rootImage): - cmd.extend([rootImage, "/mnt/dirac_container.sh"]) + cmd.extend([rootImage, f"{os.getcwd()}/dirac_container.sh"]) else: # if we are here is because there's no image, or it is not accessible (e.g. not on CVMFS) gLogger.error("Apptainer image to exec not found: ", rootImage)