Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/config_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,7 @@ ReFrame can launch containerized applications, but you need to configure properl
The type of the container platform.
Available values are the following:

- ``Apptainer``: The `Apptainer <https://apptainer.org/>`__ container runtime.
- ``Docker``: The `Docker <https://www.docker.com/>`__ container runtime.
- ``Sarus``: The `Sarus <https://sarus.readthedocs.io/>`__ container runtime.
- ``Shifter``: The `Shifter <https://github.com/NERSC/shifter>`__ container runtime.
Expand Down
18 changes: 16 additions & 2 deletions reframe/core/containers.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ class Singularity(ContainerPlatform):
def __init__(self):
super().__init__()
self.with_cuda = False
self._launch_command = 'singularity'

def emit_prepare_commands(self, stagedir):
return []
Expand All @@ -259,10 +260,23 @@ def launch_command(self, stagedir):

run_opts += self.options
if self.command:
return (f'singularity exec {" ".join(run_opts)} '
return (f'{self._launch_command} exec {" ".join(run_opts)} '
f'{self.image} {self.command}')

return f'singularity run {" ".join(run_opts)} {self.image}'
return f'{self._launch_command} run {" ".join(run_opts)} {self.image}'


class Apptainer(Singularity):
'''Container platform backend for running containers with `Apptainer
<https://apptainer.org/>`__.

.. versionadded:: 4.0.0

'''

def __init__(self):
super().__init__()
self._launch_command = 'apptainer'


class ContainerPlatformField(fields.TypedField):
Expand Down
35 changes: 32 additions & 3 deletions unittests/test_containers.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
'Sarus', 'Sarus+nocommand', 'Sarus+nopull', 'Sarus+mpi', 'Sarus+load',
'Shifter', 'Shifter+nocommand', 'Shifter+mpi', 'Shifter+nopull',
'Shifter+load',
'Singularity', 'Singularity+nocommand', 'Singularity+cuda'
'Singularity', 'Singularity+nocommand', 'Singularity+cuda',
'Apptainer', 'Apptainer+nocommand', 'Apptainer+cuda'
])
def container_variant(request):
return request.param
Expand Down Expand Up @@ -101,7 +102,7 @@ def expected_cmd_mount_points(container_variant):
'--mount=type=bind,source="/path/two",destination="/two" '
'--mount=type=bind,source="/foo",destination="/rfm_workdir" '
'load/library/image:tag cmd')
elif container_variant in {'Singularity', 'Singularity+nopull'}:
elif container_variant == 'Singularity':
return ('singularity exec -B"/path/one:/one" '
'-B"/path/two:/two" -B"/foo:/rfm_workdir" '
'--pwd /rfm_workdir image:tag cmd')
Expand All @@ -113,6 +114,19 @@ def expected_cmd_mount_points(container_variant):
return ('singularity run -B"/path/one:/one" '
'-B"/path/two:/two" -B"/foo:/rfm_workdir" '
'--pwd /rfm_workdir image:tag')
elif container_variant == 'Apptainer':
return ('apptainer exec -B"/path/one:/one" '
'-B"/path/two:/two" -B"/foo:/rfm_workdir" '
'--pwd /rfm_workdir image:tag cmd')
elif container_variant == 'Apptainer+cuda':
return ('apptainer exec -B"/path/one:/one" '
'-B"/path/two:/two" -B"/foo:/rfm_workdir" --nv '
'--pwd /rfm_workdir image:tag cmd')
elif container_variant == 'Apptainer+nocommand':
return ('apptainer run -B"/path/one:/one" '
'-B"/path/two:/two" -B"/foo:/rfm_workdir" '
'--pwd /rfm_workdir image:tag')



@pytest.fixture
Expand Down Expand Up @@ -180,7 +194,7 @@ def expected_cmd_run_opts(container_variant):
'--mount=type=bind,source="/path/one",destination="/one" '
'--mount=type=bind,source="/foo",destination="/rfm_workdir" '
'--mpi --foo --bar image:tag cmd')
elif container_variant in {'Singularity'}:
elif container_variant == 'Singularity':
return ('singularity exec -B"/path/one:/one" -B"/foo:/rfm_workdir" '
'--foo --bar image:tag cmd')
elif container_variant == 'Singularity+cuda':
Expand All @@ -189,6 +203,15 @@ def expected_cmd_run_opts(container_variant):
elif container_variant == 'Singularity+nocommand':
return ('singularity run -B"/path/one:/one" -B"/foo:/rfm_workdir" '
'--foo --bar image:tag')
elif container_variant == 'Apptainer':
return ('apptainer exec -B"/path/one:/one" -B"/foo:/rfm_workdir" '
'--foo --bar image:tag cmd')
elif container_variant == 'Apptainer+cuda':
return ('apptainer exec -B"/path/one:/one" -B"/foo:/rfm_workdir" '
'--nv --foo --bar image:tag cmd')
elif container_variant == 'Apptainer+nocommand':
return ('apptainer run -B"/path/one:/one" -B"/foo:/rfm_workdir" '
'--foo --bar image:tag')


def test_mount_points(container_platform, expected_cmd_mount_points):
Expand Down Expand Up @@ -245,6 +268,9 @@ def expected_run_with_commands(container_variant_noopt):
elif container_variant_noopt == 'Singularity':
return ("singularity exec -B\"/foo:/rfm_workdir\" "
"--foo image:tag bash -c 'cmd1; cmd2'")
elif container_variant_noopt == 'Apptainer':
return ("apptainer exec -B\"/foo:/rfm_workdir\" "
"--foo image:tag bash -c 'cmd1; cmd2'")


@pytest.fixture
Expand All @@ -267,6 +293,9 @@ def expected_run_with_workdir(container_variant_noopt):
elif container_variant_noopt == 'Singularity':
return ('singularity exec -B\"/foo:/rfm_workdir\" --pwd foodir '
'--foo image:tag cmd1')
elif container_variant_noopt == 'Apptainer':
return ('apptainer exec -B\"/foo:/rfm_workdir\" --pwd foodir '
'--foo image:tag cmd1')


def test_run_with_workdir(container_platform_with_opts,
Expand Down