diff --git a/config/cscs.py b/config/cscs.py index 4016a28a7b..f4792df03f 100644 --- a/config/cscs.py +++ b/config/cscs.py @@ -68,6 +68,11 @@ class ReframeSettings: }, 'compute': { 'scheduler': 'nativeslurm', + 'container_platforms': { + 'ShifterNG': { + 'modules': ['shifter-ng'] + } + }, 'environs': ['PrgEnv-cray', 'PrgEnv-gnu', 'PrgEnv-intel', 'PrgEnv-pgi'], 'descr': 'Intel Xeon Phi', @@ -94,6 +99,14 @@ class ReframeSettings: 'gpu': { 'scheduler': 'nativeslurm', + 'container_platforms': { + 'ShifterNG': { + 'modules': ['shifter-ng'] + }, + 'Singularity': { + 'modules': ['singularity'] + } + }, 'modules': ['daint-gpu'], 'access': ['--constraint=gpu'], 'environs': ['PrgEnv-cray', 'PrgEnv-gnu', @@ -107,6 +120,14 @@ class ReframeSettings: 'mc': { 'scheduler': 'nativeslurm', + 'container_platforms': { + 'ShifterNG': { + 'modules': ['shifter-ng'] + }, + 'Singularity': { + 'modules': ['singularity'] + } + }, 'modules': ['daint-mc'], 'access': ['--constraint=mc'], 'environs': ['PrgEnv-cray', 'PrgEnv-gnu', @@ -140,6 +161,11 @@ class ReframeSettings: 'gpu': { 'scheduler': 'nativeslurm', + 'container_platforms': { + 'Singularity': { + 'modules': ['singularity'] + }, + }, 'modules': ['daint-gpu'], 'access': ['--constraint=gpu'], 'environs': ['PrgEnv-cray', 'PrgEnv-cray_classic', @@ -154,6 +180,11 @@ class ReframeSettings: 'mc': { 'scheduler': 'nativeslurm', + 'container_platforms': { + 'Singularity': { + 'modules': ['singularity'] + }, + }, 'modules': ['daint-mc'], 'access': ['--constraint=mc'], 'environs': ['PrgEnv-cray', 'PrgEnv-cray_classic', @@ -293,7 +324,7 @@ class ReframeSettings: 'modules': [], 'access': [], 'environs': ['builtin-gcc'], - 'descr': 'Login nodes' + 'descr': 'Login nodes', } } } diff --git a/reframe/core/config.py b/reframe/core/config.py index ecb4af757f..f4344ab47b 100644 --- a/reframe/core/config.py +++ b/reframe/core/config.py @@ -199,14 +199,25 @@ def create_env(system, partition, name): part_access = partconfig.get('access', []) part_resources = partconfig.get('resources', {}) part_max_jobs = partconfig.get('max_jobs', 1) - system.add_partition(SystemPartition(name=part_name, - descr=part_descr, - scheduler=part_scheduler, - launcher=part_launcher, - access=part_access, - environs=part_environs, - resources=part_resources, - local_env=part_local_env, - max_jobs=part_max_jobs)) + part = SystemPartition(name=part_name, + descr=part_descr, + scheduler=part_scheduler, + launcher=part_launcher, + access=part_access, + environs=part_environs, + resources=part_resources, + local_env=part_local_env, + max_jobs=part_max_jobs) + + container_platforms = partconfig.get('container_platforms', {}) + for cp, env_spec in container_platforms.items(): + cp_env = m_env.Environment( + name='__rfm_env_%s' % cp, + modules=env_spec.get('modules', []), + variables=env_spec.get('variables', {}) + ) + part.add_container_env(cp, cp_env) + + system.add_partition(part) self._systems[sys_name] = system diff --git a/reframe/core/containers.py b/reframe/core/containers.py index a10d508538..763629edf9 100644 --- a/reframe/core/containers.py +++ b/reframe/core/containers.py @@ -9,27 +9,24 @@ class ContainerPlatform(abc.ABC): '''The abstract base class of any container platform. Concrete container platforms inherit from this class and must override the - :func:`emit_prepare_cmds` and :func:`emit_launch_cmds` abstract functions. + :func:`emit_prepare_commands()` and :func:`launch_command()` abstract + methods. ''' - registry = fields.TypedField('registry', str, type(None)) image = fields.TypedField('image', str, type(None)) - requires_mpi = fields.TypedField('requires_mpi', bool) commands = fields.TypedField('commands', typ.List[str]) mount_points = fields.TypedField('mount_points', typ.List[typ.Tuple[str, str]]) workdir = fields.TypedField('workdir', str, type(None)) def __init__(self): - self.registry = None self.image = None - self.requires_mpi = False self.commands = [] self.mount_points = [] - self.workdir = None + self.workdir = '/rfm_workdir' @abc.abstractmethod - def emit_prepare_cmds(self): + def emit_prepare_commands(self): '''Returns commands that are necessary before running with this container platform. @@ -41,7 +38,7 @@ def emit_prepare_cmds(self): ''' @abc.abstractmethod - def emit_launch_cmds(self): + def launch_command(self): '''Returns the command for running with this container platform. :raises: `ContainerError` in case of errors. @@ -50,10 +47,7 @@ def emit_launch_cmds(self): This method is relevant only to developers of new container platforms. ''' - if self.registry: - self.image = '/'.join([self.registry, self.image]) - @abc.abstractmethod def validate(self): '''Validates this container platform. @@ -71,21 +65,88 @@ def validate(self): class Docker(ContainerPlatform): - '''An implementation of ContainerPlatform to run containers with Docker.''' + '''An implementation of :class:`ContainerPlatform` for running containers + with Docker.''' - def emit_prepare_cmds(self): - pass + def emit_prepare_commands(self): + return [] - def emit_launch_cmds(self): - super().emit_launch_cmds() - docker_opts = ['-v "%s":"%s"' % mp for mp in self.mount_points] - run_cmd = 'docker run %s %s bash -c ' % (' '.join(docker_opts), - self.image) + def launch_command(self): + super().launch_command() + run_opts = ['-v "%s":"%s"' % mp for mp in self.mount_points] + run_cmd = 'docker run --rm %s %s bash -c ' % (' '.join(run_opts), + self.image) return run_cmd + "'" + '; '.join( ['cd ' + self.workdir] + self.commands) + "'" - def validate(self): - super().validate() + +class ShifterNG(ContainerPlatform): + '''An implementation of :class:`ContainerPlatform` for running containers + with ShifterNG.''' + + #: Add an option to the launch command to enable MPI support. + #: + #: :type: boolean + #: :default: :class:`False` + with_mpi = fields.TypedField('with_mpi', bool) + + def __init__(self): + super().__init__() + self.with_mpi = False + self._command = 'shifter' + + def emit_prepare_commands(self): + return [self._command + ' pull %s' % self.image] + + def launch_command(self): + super().launch_command() + run_opts = ['--mount=type=bind,source="%s",destination="%s"' % + mp for mp in self.mount_points] + if self.with_mpi: + run_opts.append('--mpi') + + run_cmd = self._command + ' run %s %s bash -c ' % (' '.join(run_opts), + self.image) + return run_cmd + "'" + '; '.join( + ['cd ' + self.workdir] + self.commands) + "'" + + +class Sarus(ShifterNG): + '''An implementation of :class:`ContainerPlatform` for running containers + with Sarus.''' + + def __init__(self): + super().__init__() + self._command = 'sarus' + + +class Singularity(ContainerPlatform): + '''An implementation of :class:`ContainerPlatform` for running containers + with Singularity.''' + + #: Add an option to the launch command to enable CUDA support. + #: + #: :type: boolean + #: :default: :class:`False` + with_cuda = fields.TypedField('with_cuda', bool) + + def __init__(self): + super().__init__() + self.with_cuda = False + + def emit_prepare_commands(self): + return [] + + def launch_command(self): + super().launch_command() + exec_opts = ['-B"%s:%s"' % mp for mp in self.mount_points] + if self.with_cuda: + exec_opts.append('--nv') + + run_cmd = 'singularity exec %s %s bash -c ' % (' '.join(exec_opts), + self.image) + return run_cmd + "'" + '; '.join( + ['cd ' + self.workdir] + self.commands) + "'" class ContainerPlatformField(fields.TypedField): diff --git a/reframe/core/pipeline.py b/reframe/core/pipeline.py index 18b6e71a6c..24016e76dd 100644 --- a/reframe/core/pipeline.py +++ b/reframe/core/pipeline.py @@ -21,6 +21,7 @@ import reframe.utility.os_ext as os_ext import reframe.utility.typecheck as typ from reframe.core.buildsystems import BuildSystemField +from reframe.core.containers import ContainerPlatform, ContainerPlatformField from reframe.core.deferrable import deferrable, _DeferredExpression, evaluate from reframe.core.exceptions import (BuildError, DependencyError, PipelineError, SanityError, @@ -221,6 +222,19 @@ class RegressionTest(metaclass=RegressionTestMeta): #: :default: ``[]`` executable_opts = fields.TypedField('executable_opts', typ.List[str]) + #: The container platform to be used for this test. + #: + #: If the `self.container_platform` is defined on the test, both + #: `self.executable` and `self.executable_opts` are ignored. + #: + #: :type: :class:`str` or + #: :class:`reframe.core.containers.ContainerPlatform`. + #: :default: :class:`None`. + #: + #: .. versionadded:: 2.19 + container_platform = ContainerPlatformField( + 'container_platform', type(None)) + #: List of shell commands to execute before launching this job. #: #: These commands do not execute in the context of ReFrame. @@ -609,6 +623,7 @@ def _rfm_init(self, name=None, prefix=None): self.tags = set() self.maintainers = [] self._perfvalues = {} + self.container_platform = None # Strict performance check, if applicable self.strict_check = True @@ -1071,6 +1086,27 @@ def run(self): if not self.current_system or not self._current_partition: raise PipelineError('no system or system partition is set') + if self.container_platform: + try: + cp_name = type(self.container_platform).__name__ + cp_env = self._current_partition.container_environs[cp_name] + except KeyError as e: + raise PipelineError( + 'container platform not configured ' + 'on the current partition: %s' % e) from None + + self.container_platform.validate() + self.container_platform.mount_points += [ + (self._stagedir, self.container_platform.workdir) + ] + + # We replace executable and executable_opts in case of containers + self.executable = self.container_platform.launch_command() + self.executable_opts = [] + prepare_container = self.container_platform.emit_prepare_commands() + if prepare_container: + self.pre_run += prepare_container + self.job.num_tasks = self.num_tasks self.job.num_tasks_per_node = self.num_tasks_per_node self.job.num_tasks_per_core = self.num_tasks_per_core @@ -1083,8 +1119,20 @@ def run(self): commands = [*self.pre_run, ' '.join(exec_cmd), *self.post_run] user_environ = env.Environment(type(self).__name__, self.modules, self.variables.items()) - environs = [self._current_partition.local_env, self._current_environ, - user_environ, self._cdt_environ] + environs = [ + self._current_partition.local_env, + self._current_environ, + user_environ, + self._cdt_environ + ] + if self.container_platform and cp_env: + environs = [ + self._current_partition.local_env, + self._current_environ, + cp_env, + user_environ, + self._cdt_environ + ] with os_ext.change_dir(self._stagedir): try: diff --git a/reframe/core/systems.py b/reframe/core/systems.py index fce086abc2..77a443631c 100644 --- a/reframe/core/systems.py +++ b/reframe/core/systems.py @@ -19,6 +19,8 @@ class SystemPartition: _environs = fields.TypedField('_environs', typ.List[Environment]) _resources = fields.TypedField('_resources', typ.Dict[str, typ.List[str]]) _local_env = fields.TypedField('_local_env', Environment, type(None)) + _container_environs = fields.TypedField('_container_environs', + typ.Dict[str, Environment]) # maximum concurrent jobs _max_jobs = fields.TypedField('_max_jobs', int) @@ -35,6 +37,7 @@ def __init__(self, name, descr=None, scheduler=None, launcher=None, self._resources = dict(resources) self._max_jobs = max_jobs self._local_env = local_env + self._container_environs = {} # Parent system self._system = None @@ -52,6 +55,10 @@ def descr(self): def environs(self): return utility.SequenceView(self._environs) + @property + def container_environs(self): + return utility.MappingView(self._container_environs) + @property def fullname(self): '''Return the fully-qualified name of this partition. @@ -111,6 +118,9 @@ def launcher(self): ''' return self._launcher + def add_container_env(self, env_name, environ): + self._container_environs[env_name] = environ + # Instantiate managed resource `name` with `value`. def get_resource(self, name, **values): ret = [] diff --git a/unittests/test_containers.py b/unittests/test_containers.py index 96b5f2f346..ae616e6687 100644 --- a/unittests/test_containers.py +++ b/unittests/test_containers.py @@ -13,12 +13,17 @@ def create_container_platform(self): @property @abc.abstractmethod - def exp_cmd_mount_points(self): + def expected_cmd_mount_points(self): pass @property @abc.abstractmethod - def exp_cmd_custom_registry(self): + def expected_cmd_prepare(self): + pass + + @property + @abc.abstractmethod + def expected_cmd_with_run_opts(self): pass def setUp(self): @@ -30,8 +35,8 @@ def test_mount_points(self): ('/path/two', '/two')] self.container_platform.commands = ['cmd1', 'cmd2'] self.container_platform.workdir = '/stagedir' - assert (self.exp_cmd_mount_points == - self.container_platform.emit_launch_cmds()) + assert (self.expected_cmd_mount_points == + self.container_platform.launch_command()) def test_missing_image(self): self.container_platform.commands = ['cmd'] @@ -43,14 +48,18 @@ def test_missing_commands(self): with pytest.raises(ContainerError): self.container_platform.validate() - def test_custom_registry(self): - self.container_platform.registry = 'registry/custom' + def test_prepare_command(self): + self.container_platform.image = 'name:tag' + assert (self.expected_cmd_prepare == + self.container_platform.emit_prepare_commands()) + + def test_run_opts(self): self.container_platform.image = 'name:tag' self.container_platform.commands = ['cmd'] self.container_platform.mount_points = [('/path/one', '/one')] self.container_platform.workdir = '/stagedir' - assert (self.exp_cmd_custom_registry == - self.container_platform.emit_launch_cmds()) + assert (self.expected_cmd_with_run_opts == + self.container_platform.launch_command()) class TestDocker(_ContainerPlatformTest, unittest.TestCase): @@ -58,11 +67,141 @@ def create_container_platform(self): return containers.Docker() @property - def exp_cmd_mount_points(self): - return ('docker run -v "/path/one":"/one" -v "/path/two":"/two" ' + def expected_cmd_mount_points(self): + return ('docker run --rm -v "/path/one":"/one" -v "/path/two":"/two" ' + "name:tag bash -c 'cd /stagedir; cmd1; cmd2'") + + @property + def expected_cmd_prepare(self): + return [] + + @property + def expected_cmd_with_run_opts(self): + return ('docker run --rm -v "/path/one":"/one" ' + "name:tag bash -c 'cd /stagedir; cmd'") + + +class TestShifterNG(_ContainerPlatformTest, unittest.TestCase): + def create_container_platform(self): + return containers.ShifterNG() + + @property + def expected_cmd_mount_points(self): + return ('shifter run ' + '--mount=type=bind,source="/path/one",destination="/one" ' + '--mount=type=bind,source="/path/two",destination="/two" ' "name:tag bash -c 'cd /stagedir; cmd1; cmd2'") @property - def exp_cmd_custom_registry(self): - return ('docker run -v "/path/one":"/one" registry/custom/name:tag ' - "bash -c 'cd /stagedir; cmd'") + def expected_cmd_prepare(self): + return ['shifter pull name:tag'] + + @property + def expected_cmd_with_run_opts(self): + return ('shifter run ' + '--mount=type=bind,source="/path/one",destination="/one" ' + "name:tag bash -c 'cd /stagedir; cmd'") + + +class TestShifterNGWithMPI(TestShifterNG): + def create_container_platform(self): + ret = containers.ShifterNG() + ret.with_mpi = True + return ret + + @property + def expected_cmd_mount_points(self): + return ('shifter run ' + '--mount=type=bind,source="/path/one",destination="/one" ' + '--mount=type=bind,source="/path/two",destination="/two" ' + "--mpi name:tag bash -c 'cd /stagedir; cmd1; cmd2'") + + @property + def expected_cmd_with_run_opts(self): + return ('shifter run ' + '--mount=type=bind,source="/path/one",destination="/one" ' + "--mpi name:tag bash -c 'cd /stagedir; cmd'") + + +class TestSarus(_ContainerPlatformTest, unittest.TestCase): + def create_container_platform(self): + return containers.Sarus() + + @property + def expected_cmd_mount_points(self): + return ('sarus run ' + '--mount=type=bind,source="/path/one",destination="/one" ' + '--mount=type=bind,source="/path/two",destination="/two" ' + "name:tag bash -c 'cd /stagedir; cmd1; cmd2'") + + @property + def expected_cmd_prepare(self): + return ['sarus pull name:tag'] + + @property + def expected_cmd_with_run_opts(self): + return ('sarus run ' + '--mount=type=bind,source="/path/one",destination="/one" ' + "name:tag bash -c 'cd /stagedir; cmd'") + + +class TestSarusWithMPI(TestSarus): + def create_container_platform(self): + ret = containers.Sarus() + ret.with_mpi = True + return ret + + @property + def expected_cmd_mount_points(self): + return ('sarus run ' + '--mount=type=bind,source="/path/one",destination="/one" ' + '--mount=type=bind,source="/path/two",destination="/two" ' + "--mpi name:tag bash -c 'cd /stagedir; cmd1; cmd2'") + + @property + def expected_cmd_with_run_opts(self): + self.container_platform.with_mpi = True + return ('sarus run ' + '--mount=type=bind,source="/path/one",destination="/one" ' + "--mpi name:tag bash -c 'cd /stagedir; cmd'") + + +class TestSingularity(_ContainerPlatformTest, unittest.TestCase): + def create_container_platform(self): + return containers.Singularity() + + @property + def expected_cmd_mount_points(self): + return ('singularity exec -B"/path/one:/one" -B"/path/two:/two" ' + "name:tag bash -c 'cd /stagedir; cmd1; cmd2'") + + @property + def expected_cmd_prepare(self): + return [] + + @property + def expected_cmd_with_run_opts(self): + return ('singularity exec -B"/path/one:/one" ' + "name:tag bash -c 'cd /stagedir; cmd'") + + +class TestSingularityWithCuda(TestSingularity): + def create_container_platform(self): + ret = containers.Singularity() + ret.with_cuda = True + return ret + + @property + def expected_cmd_mount_points(self): + return ('singularity exec -B"/path/one:/one" -B"/path/two:/two" ' + "--nv name:tag bash -c 'cd /stagedir; cmd1; cmd2'") + + @property + def expected_cmd_prepare(self): + return [] + + @property + def expected_cmd_with_run_opts(self): + self.container_platform.with_cuda = True + return ('singularity exec -B"/path/one:/one" ' + "--nv name:tag bash -c 'cd /stagedir; cmd'") diff --git a/unittests/test_pipeline.py b/unittests/test_pipeline.py index c545e848e6..e74fa96d9b 100644 --- a/unittests/test_pipeline.py +++ b/unittests/test_pipeline.py @@ -1,5 +1,6 @@ import os import pytest +import re import tempfile import unittest @@ -14,6 +15,26 @@ from reframe.frontend.loader import RegressionCheckLoader +def _setup_local_execution(): + partition = rt.runtime().system.partition('login') + environ = partition.environment('builtin-gcc') + return partition, environ + + +def _setup_remote_execution(scheduler=None): + partition = fixtures.partition_with_scheduler(scheduler) + if partition is None: + pytest.skip('job submission not supported') + + try: + environ = partition.environs[0] + except IndexError: + pytest.skip('no environments configured for partition: %s' % + partition.fullname) + + return partition, environ + + def _run(test, partition, prgenv): test.setup(partition, prgenv) test.compile() @@ -25,24 +46,18 @@ def _run(test, partition, prgenv): test.cleanup(remove_files=True) -class TestRegressionTest(unittest.TestCase): - def setup_local_execution(self): - self.partition = rt.runtime().system.partition('login') - self.prgenv = self.partition.environment('builtin-gcc') +def _cray_cle_version(): + completed = os_ext.run_command('cat /etc/opt/cray/release/cle-release') + matched = re.match(r'^RELEASE=(\S+)', completed.stdout) + if matched is None: + return None - def setup_remote_execution(self): - self.partition = fixtures.partition_with_scheduler() - if self.partition is None: - self.skipTest('job submission not supported') + return matched.group(1) - try: - self.prgenv = self.partition.environs[0] - except IndexError: - self.skipTest('no environments configured for partition: %s' % - self.partition.fullname) +class TestRegressionTest(unittest.TestCase): def setUp(self): - self.setup_local_execution() + self.partition, self.prgenv = _setup_local_execution() self.loader = RegressionCheckLoader(['unittests/resources/checks']) # Set runtime prefix @@ -93,7 +108,7 @@ def _run_test(self, test, compile_only=False): @fixtures.switch_to_user_runtime def test_hellocheck(self): - self.setup_remote_execution() + self.partition, self.prgenv = _setup_remote_execution() test = self.loader.load_from_file( 'unittests/resources/checks/hellocheck.py')[0] @@ -103,7 +118,7 @@ def test_hellocheck(self): @fixtures.switch_to_user_runtime def test_hellocheck_make(self): - self.setup_remote_execution() + self.partition, self.prgenv = _setup_remote_execution() test = self.loader.load_from_file( 'unittests/resources/checks/hellocheck_make.py')[0] @@ -831,3 +846,101 @@ def extract_perf(patt, tag): self.assertIn('v1', log_output) self.assertIn('v2', log_output) self.assertIn('v3', log_output) + + +class TestRegressionTestWithContainer(unittest.TestCase): + def temp_prefix(self): + # Set runtime prefix + rt.runtime().resources.prefix = tempfile.mkdtemp(dir='unittests') + + def create_test(self, platform, image): + class ContainerTest(rfm.RunOnlyRegressionTest): + def __init__(self, platform): + self._prefix = 'unittests/resources/checks' + self.valid_prog_environs = ['*'] + self.valid_systems = ['*'] + self.container_platform = platform + self.container_platform.image = image + self.container_platform.commands = [ + 'pwd', 'ls', 'cat /etc/os-release' + ] + self.container_platform.workdir = '/workdir' + self.sanity_patterns = sn.all([ + sn.assert_found( + r'^' + self.container_platform.workdir, self.stdout), + sn.assert_found(r'^hello.c', self.stdout), + sn.assert_found( + r'18\.04\.\d+ LTS \(Bionic Beaver\)', self.stdout), + ]) + + test = ContainerTest(platform) + return test + + def _skip_if_not_configured(self, partition, platform): + if platform not in partition.container_environs.keys(): + pytest.skip('%s is not configured on the system' % platform) + + @fixtures.switch_to_user_runtime + def test_singularity(self): + cle_version = _cray_cle_version() + if cle_version is not None and cle_version.startswith('6.0'): + pytest.skip('test not supported on Cray CLE6') + + partition, environ = _setup_remote_execution() + self._skip_if_not_configured(partition, 'Singularity') + with tempfile.TemporaryDirectory(dir='unittests') as dirname: + rt.runtime().resources.prefix = dirname + _run(self.create_test('Singularity', 'docker://ubuntu:18.04'), + partition, environ) + + @fixtures.switch_to_user_runtime + def test_docker(self): + partition, environ = _setup_remote_execution('local') + self._skip_if_not_configured(partition, 'Docker') + with tempfile.TemporaryDirectory(dir='unittests') as dirname: + rt.runtime().resources.prefix = dirname + _run(self.create_test('Docker', 'ubuntu:18.04'), + partition, environ) + + @fixtures.switch_to_user_runtime + def test_shifter(self): + partition, environ = _setup_remote_execution() + self._skip_if_not_configured(partition, 'ShifterNG') + with tempfile.TemporaryDirectory(dir='unittests') as dirname: + rt.runtime().resources.prefix = dirname + _run(self.create_test('ShifterNG', 'ubuntu:18.04'), + partition, environ) + + @fixtures.switch_to_user_runtime + def test_sarus(self): + partition, environ = _setup_remote_execution() + self._skip_if_not_configured(partition, 'Sarus') + with tempfile.TemporaryDirectory(dir='unittests') as dirname: + rt.runtime().resources.prefix = dirname + _run(self.create_test('Sarus', 'ubuntu:18.04'), + partition, environ) + + def test_unknown_platform(self): + partition, environ = _setup_local_execution() + with pytest.raises(ValueError): + with tempfile.TemporaryDirectory(dir='unittests') as dirname: + rt.runtime().resources.prefix = dirname + _run(self.create_test('foo', 'ubuntu:18.04'), + partition, environ) + + def test_not_configured_platform(self): + partition, environ = _setup_local_execution() + platform = None + for cp in ['Docker', 'Singularity', 'Sarus', 'ShifterNG']: + if cp not in partition.container_environs.keys(): + platform = cp + break + + if platform is None: + pytest.skip('cannot find a not configured supported platform') + + with pytest.raises(PipelineError): + with tempfile.TemporaryDirectory(dir='unittests') as dirname: + rt.runtime().resources.prefix = dirname + _run(self.create_test(platform, 'ubuntu:18.04'), + partition, environ)