Skip to content

Commit e2fa440

Browse files
author
Vasileios Karakasis
authored
Merge pull request #985 from rsarm/containers/doc
[doc] Document the container platforms feature
2 parents 14515c9 + f72e094 commit e2fa440

File tree

8 files changed

+209
-45
lines changed

8 files changed

+209
-45
lines changed

docs/advanced.rst

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,3 +448,60 @@ Here is how the new deferred attribute is defined:
448448

449449
The behavior of the flexible task allocation is controlled by the ``--flex-alloc-tasks`` command line option.
450450
See the corresponding `section <running.html#controlling-the-flexible-task-allocation>`__ for more information.
451+
452+
453+
Testing containerized applications
454+
----------------------------------
455+
456+
.. versionadded:: 2.20
457+
458+
459+
ReFrame can be used also to test applications that run inside a container.
460+
A container-based test can be written as :class:`RunOnlyRegressionTest <reframe.core.pipeline.RunOnlyRegressionTest>` that sets the :attr:`container_platform <reframe.core.pipeline.RegressionTest.container_platform>`.
461+
The following example shows a simple test that runs some basic commands inside an Ubuntu 18.04 container and checks that the test has indeed run inside the container and that the stage directory was correctly mounted:
462+
463+
.. literalinclude:: ../tutorial/advanced/advanced_example10.py
464+
465+
A container-based test in ReFrame requires that the :attr:`container_platform <reframe.core.pipeline.RegressionTest.container_platform>` is set:
466+
467+
.. literalinclude:: ../tutorial/advanced/advanced_example10.py
468+
:lines: 13
469+
470+
This attribute accepts a string that corresponds to the name of the platform and it instantiates the appropriate :class:`ContainerPlatform <reframe.core.containers.ContainerPlatform>` object behind the scenes.
471+
In this case, the test will be using `Singularity <https://sylabs.io>`__ as a container platform.
472+
If such a platform is not configured for the current system, the test will fail.
473+
For a complete list of supported container platforms, the user is referred to the `configuration documentation <configure.html#partition-configuration>`__.
474+
475+
As soon as the container platform to be used is defined, you need to specify the container image to use and the commands to run inside the container:
476+
477+
.. literalinclude:: ../tutorial/advanced/advanced_example10.py
478+
:lines: 14-17
479+
480+
These two attributes are mandatory for container-based check.
481+
The :attr:`image <reframe.core.pipeline.RegressionTest.container_platform.image>` attribute specifies the name of an image from a registry, whereas the :attr:`commands <reframe.core.pipeline.RegressionTest.container_platform.commands>` attribute provides the list of commands to be run inside the container.
482+
It is important to note that the :attr:`executable <reframe.core.pipeline.RegressionTest.executable>` and :attr:`executable_opts <reframe.core.pipeline.RegressionTest.executable_opts>` attributes of the :class:`RegressionTest <reframe.core.pipeline.RegressionTest>` are ignored in case of container-based tests.
483+
484+
In the above example, ReFrame will run the container as follows:
485+
486+
.. code:: shell
487+
488+
singularity exec -B"/path/to/test/stagedir:/rfm_workdir" docker://ubuntu:18.04 bash -c 'cd rfm_workdir; pwd; ls; cat /etc/os-release'
489+
490+
By default ReFrame will mount the stage directory of the test under ``/rfm_workdir`` inside the container and it will always prepend a ``cd`` command to that directory.
491+
The user commands then are then run from that directory one after the other.
492+
Once the commands are executed, the container is stopped and ReFrame goes on with the sanity and/or performance checks.
493+
494+
Users may also change the default mount point of the stage directory by using :attr:`workdir <reframe.core.pipeline.RegressionTest.container_platform.workdir>` attribute:
495+
496+
.. literalinclude:: ../tutorial/advanced/advanced_example10.py
497+
:lines: 18
498+
499+
Besides the stage directory, additional mount points can be specified through the :attr:`mount_points <reframe.core.pipeline.RegressionTest.container_platform.mount_points>` attribute:
500+
501+
.. code-block:: python
502+
503+
self.container_platform.mount_points = [('/path/to/host/dir1', '/path/to/container/mount_point1'),
504+
('/path/to/host/dir2', '/path/to/container/mount_point2')]
505+
506+
507+
For a complete list of the available attributes of a specific container platform, the reader is referred to `reference guide <reference.html#container-platforms>`__.

docs/configure.rst

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ The following example shows a minimal configuration for the `Piz Daint <https://
4343
'access': ['--constraint=gpu'],
4444
'environs': ['PrgEnv-cray', 'PrgEnv-gnu',
4545
'PrgEnv-intel', 'PrgEnv-pgi'],
46+
'container_platforms': {
47+
'Singularity': {
48+
'modules': ['Singularity']
49+
}
50+
},
4651
'descr': 'Hybrid nodes (Haswell/P100)',
4752
'max_jobs': 100
4853
},
@@ -53,6 +58,11 @@ The following example shows a minimal configuration for the `Piz Daint <https://
5358
'access': ['--constraint=mc'],
5459
'environs': ['PrgEnv-cray', 'PrgEnv-gnu',
5560
'PrgEnv-intel', 'PrgEnv-pgi'],
61+
'container_platforms': {
62+
'Singularity': {
63+
'modules': ['Singularity']
64+
}
65+
},
5666
'descr': 'Multicore nodes (Broadwell)',
5767
'max_jobs': 100
5868
}
@@ -150,6 +160,33 @@ The available partition attributes are the following:
150160
* ``environs``: A list of environments, with which ReFrame will try to run any regression tests written for this partition (default ``[]``).
151161
The environment names must be resolved inside the ``environments`` section of the ``site_configuration`` dictionary (see `Environments Configuration <#environments-configuration>`__ for more information).
152162

163+
* ``container_platforms``: *[new in 2.20]* A set of key/value pairs specifying the supported container platforms for this partition and how their environment is set up.
164+
Supported platform names are the following (names are case sensitive):
165+
166+
- ``Docker``: The `Docker <https://www.docker.com/>`__ container runtime.
167+
- ``Singularity``: The `Singularity <https://sylabs.io/>`__ container runtime.
168+
- ``Sarus``: The `Sarus <https://sarus.readthedocs.io>`__ container runtime.
169+
170+
Each configured container runtime is associated optionally with an environment (modules and environment variables) that is providing it.
171+
This environment is specified as a dictionary in the following format:
172+
173+
.. code:: python
174+
175+
{
176+
'modules': ['mod1', 'mod2', ...]
177+
'variables': {'ENV1': 'VAL1', 'ENV2': 'VAL2', ...}
178+
}
179+
180+
181+
If no special environment arrangement is needed for a configured container platform, you can simply specify an empty dictionary as an environment configuration, as it is shown in the following example:
182+
183+
.. code:: python
184+
185+
'container_platforms': {
186+
'Docker': {}
187+
}
188+
189+
153190
* ``modules``: A list of modules to be loaded before running a regression test on that partition (default ``[]``).
154191

155192
* ``variables``: A set of environment variables to be set before running a regression test on that partition (default ``{}``).

docs/reference.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,3 +137,19 @@ It is up to the concrete build system implementations on how to use or not these
137137
:members:
138138
:exclude-members: BuildSystemField
139139
:show-inheritance:
140+
141+
142+
Container platforms
143+
-------------------
144+
145+
.. versionadded:: 2.20
146+
147+
ReFrame can run a regression test inside a container.
148+
To achieve that you have to set the :attr:`reframe.core.pipeline.RegressionTest.container_platform` attribute and then set up the container platform (e.g., image to load, commands to execute).
149+
The :class:`reframe.core.ContainerPlatform` abstract base class define the basic interface and a minimal set of attributes that all concrete container platforms must implement.
150+
Concrete container platforms may also define additional fields that are specific to them.
151+
152+
.. automodule:: reframe.core.containers
153+
:members:
154+
:exclude-members: ContainerPlatformField
155+
:show-inheritance:

reframe/core/buildsystems.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -644,12 +644,6 @@ def emit_build_commands(self, environ):
644644

645645

646646
class BuildSystemField(fields.TypedField):
647-
'''A field representing a build system.
648-
649-
You may either assign an instance of :class:`BuildSystem` or a string
650-
representing the name of the concrete class of a build system.
651-
'''
652-
653647
def __init__(self, fieldname, *other_types):
654648
super().__init__(fieldname, BuildSystem, *other_types)
655649

reframe/core/containers.py

Lines changed: 43 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,39 @@ class ContainerPlatform(abc.ABC):
99
'''The abstract base class of any container platform.
1010
1111
Concrete container platforms inherit from this class and must override the
12-
:func:`emit_prepare_commands()` and :func:`launch_command()` abstract
13-
methods.
12+
:func:`emit_prepare_commands` and :func:`launch_command` abstract methods.
1413
'''
1514

15+
#: The container image to be used for running the test.
16+
#:
17+
#: :type: :class:`str` or :class:`None`
18+
#: :default: :class:`None`
1619
image = fields.TypedField('image', str, type(None))
20+
21+
#: The commands to be executed within the container.
22+
#:
23+
#: :type: :class:`list[str]`
24+
#: :default: ``[]``
1725
commands = fields.TypedField('commands', typ.List[str])
26+
27+
#: List of mount point pairs for directories to mount inside the container.
28+
#:
29+
#: Each mount point is specified as a tuple of
30+
#: ``(/path/in/host, /path/in/container)``.
31+
#:
32+
#: :type: :class:`list[tuple[str, str]]`
33+
#: :default: ``[]``
1834
mount_points = fields.TypedField('mount_points',
1935
typ.List[typ.Tuple[str, str]])
36+
37+
#: The working directory of ReFrame inside the container.
38+
#:
39+
#: This is the directory where the test's stage directory is mounted inside
40+
#: the container. This directory is always mounted regardless if
41+
#: :attr:`mount_points` is set or not.
42+
#:
43+
#: :type: :class:`str`
44+
#: :default: ``/rfm_workdir``
2045
workdir = fields.TypedField('workdir', str, type(None))
2146

2247
def __init__(self):
@@ -27,36 +52,30 @@ def __init__(self):
2752

2853
@abc.abstractmethod
2954
def emit_prepare_commands(self):
30-
'''Returns commands that are necessary before running with this
31-
container platform.
55+
'''Returns commands for preparing this container for running.
3256
33-
:raises: `ContainerError` in case of errors.
57+
Such a command could be for pulling the container image from a
58+
repository.
3459
3560
.. note:
61+
3662
This method is relevant only to developers of new container
37-
platforms.
63+
platform backends.
64+
3865
'''
3966

4067
@abc.abstractmethod
4168
def launch_command(self):
42-
'''Returns the command for running with this container platform.
43-
44-
:raises: `ContainerError` in case of errors.
69+
'''Returns the command for running :attr:`commands` with this container
70+
platform.
4571
4672
.. note:
4773
This method is relevant only to developers of new container
4874
platforms.
75+
4976
'''
5077

5178
def validate(self):
52-
'''Validates this container platform.
53-
54-
:raises: `ContainerError` in case of errors.
55-
56-
.. note:
57-
This method is relevant only to developers of new container
58-
platforms.
59-
'''
6079
if self.image is None:
6180
raise ContainerError('no image specified')
6281

@@ -65,8 +84,8 @@ def validate(self):
6584

6685

6786
class Docker(ContainerPlatform):
68-
'''An implementation of :class:`ContainerPlatform` for running containers
69-
with Docker.'''
87+
'''Container platform backend for running containers with `Docker
88+
<https://www.docker.com/>`__.'''
7089

7190
def emit_prepare_commands(self):
7291
return []
@@ -81,8 +100,7 @@ def launch_command(self):
81100

82101

83102
class ShifterNG(ContainerPlatform):
84-
'''An implementation of :class:`ContainerPlatform` for running containers
85-
with ShifterNG.'''
103+
'''Container platform backend for running containers with ShifterNG.'''
86104

87105
#: Add an option to the launch command to enable MPI support.
88106
#:
@@ -112,19 +130,18 @@ def launch_command(self):
112130

113131

114132
class Sarus(ShifterNG):
115-
'''An implementation of :class:`ContainerPlatform` for running containers
116-
with Sarus.'''
133+
'''Container platform backend for running containers with Sarus.'''
117134

118135
def __init__(self):
119136
super().__init__()
120137
self._command = 'sarus'
121138

122139

123140
class Singularity(ContainerPlatform):
124-
'''An implementation of :class:`ContainerPlatform` for running containers
125-
with Singularity.'''
141+
'''Container platform backend for running containers with `Singularity
142+
<https://sylabs.io/>`__.'''
126143

127-
#: Add an option to the launch command to enable CUDA support.
144+
#: Enable CUDA support when launching the container.
128145
#:
129146
#: :type: boolean
130147
#: :default: :class:`False`
@@ -150,12 +167,6 @@ def launch_command(self):
150167

151168

152169
class ContainerPlatformField(fields.TypedField):
153-
'''A field representing a container platforms.
154-
155-
You may either assign an instance of :class:`ContainerPlatform:` or a
156-
string representing the name of the concrete class of a container platform.
157-
'''
158-
159170
def __init__(self, fieldname, *other_types):
160171
super().__init__(fieldname, ContainerPlatform, *other_types)
161172

reframe/core/pipeline.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -236,18 +236,31 @@ class RegressionTest(metaclass=RegressionTestMeta):
236236
#: :default: ``[]``
237237
executable_opts = fields.TypedField('executable_opts', typ.List[str])
238238

239-
#: The container platform to be used for this test.
239+
#: The container platform to be used for launching this test.
240240
#:
241-
#: If the `self.container_platform` is defined on the test, both
242-
#: `self.executable` and `self.executable_opts` are ignored.
241+
#: If this field is set, the test will run inside a container using the
242+
#: specified container runtime. Container-specific options must be defined
243+
#: additionally after this field is set:
244+
#:
245+
#: .. code:: python
246+
#:
247+
#: self.container_platform = 'Singularity'
248+
#: self.container_platform.image = 'docker://ubuntu:18.04'
249+
#: self.container_platform.commands = ['cat /etc/os-release']
250+
#:
251+
#: If this field is set, :attr:`executable` and :attr:`executable_opts`
252+
#: attributes are ignored. The container platform's :attr:`commands` will
253+
#: be used instead. For more information on the container platform support,
254+
#: see the `tutorial <advanced.html#testing-containerized-applications>`__
255+
#: and the `reference guide <reference.html#container-platforms>`__.
243256
#:
244257
#: :type: :class:`str` or
245-
#: :class:`reframe.core.containers.ContainerPlatform`.
258+
#: :class:`reframe.core.containers.ContainerPlatform`.
246259
#: :default: :class:`None`.
247260
#:
248-
#: .. versionadded:: 2.19
249-
container_platform = ContainerPlatformField(
250-
'container_platform', type(None))
261+
#: .. versionadded:: 2.20
262+
container_platform = ContainerPlatformField('container_platform',
263+
type(None))
251264

252265
#: List of shell commands to execute before launching this job.
253266
#:
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import reframe as rfm
2+
import reframe.utility.sanity as sn
3+
4+
5+
@rfm.required_version('>=2.20-dev2')
6+
@rfm.simple_test
7+
class Example10Test(rfm.RunOnlyRegressionTest):
8+
def __init__(self, **kwargs):
9+
super().__init__()
10+
self.descr = 'Run commands inside a container'
11+
self.valid_systems = ['daint:gpu']
12+
self.valid_prog_environs = ['PrgEnv-cray']
13+
self.container_platform = 'Singularity'
14+
self.container_platform.image = 'docker://ubuntu:18.04'
15+
self.container_platform.commands = [
16+
'pwd', 'ls', 'cat /etc/os-release'
17+
]
18+
self.container_platform.workdir = '/workdir'
19+
self.sanity_patterns = sn.all([
20+
sn.assert_found(r'^' + self.container_platform.workdir,
21+
self.stdout),
22+
sn.assert_found(r'^advanced_example1.c', self.stdout),
23+
sn.assert_found(r'18.04.\d+ LTS \(Bionic Beaver\)', self.stdout),
24+
])
25+
self.maintainers = ['put-your-name-here']
26+
self.tags = {'tutorial'}

tutorial/config/settings.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ class ReframeSettings:
3131
'access': ['--constraint=gpu'],
3232
'environs': ['PrgEnv-cray', 'PrgEnv-gnu',
3333
'PrgEnv-intel', 'PrgEnv-pgi'],
34+
'container_platforms': {
35+
'Singularity': {
36+
'modules': ['Singularity']
37+
}
38+
},
3439
'descr': 'Hybrid nodes (Haswell/P100)',
3540
'max_jobs': 100
3641
},
@@ -41,6 +46,11 @@ class ReframeSettings:
4146
'access': ['--constraint=mc'],
4247
'environs': ['PrgEnv-cray', 'PrgEnv-gnu',
4348
'PrgEnv-intel', 'PrgEnv-pgi'],
49+
'container_platforms': {
50+
'Singularity': {
51+
'modules': ['Singularity']
52+
}
53+
},
4454
'descr': 'Multicore nodes (Broadwell)',
4555
'max_jobs': 100
4656
}

0 commit comments

Comments
 (0)