Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[enhancement] Skip modules system sanity checking when module conflict resolution is off #3054

Merged
merged 11 commits into from
Dec 8, 2023
10 changes: 9 additions & 1 deletion docs/config_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,17 @@ System Configuration
- ``spack``: `Spack <https://spack.readthedocs.io/en/latest/>`__'s built-in mechanism for managing modules.
- ``nomod``: This is to denote that no modules system is used by this system.

.. versionadded:: 3.4
Normally, upon loading the configuration of the system ReFrame checks that a sane installation exists for the modules system requested and will issue an error if it fails to find one.
The modules system sanity check is skipped when the :attr:`~config.general.resolve_module_conflicts` is set to :obj:`False`.
This is useful in cases where the current system does not have a modules system but the remote partitions have one and you would like ReFrame to generate the module commands.

.. versionadded:: 3.4
The ``spack`` backend is added.

.. versionchanged:: 4.5.0
The modules system sanity check is skipped when the :attr:`config.general.resolve_module_conflicts` is not set.


.. py:attribute:: systems.modules

:required: No
Expand Down
144 changes: 104 additions & 40 deletions reframe/core/modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,24 +106,25 @@
module_map = fields.TypedField(types.Dict[str, types.List[str]])

@classmethod
def create(cls, modules_kind=None):
def create(cls, modules_kind=None, validate=True):
getlogger().debug(f'Initializing modules system {modules_kind!r}')
if modules_kind is None or modules_kind == 'nomod':
return ModulesSystem(NoModImpl())
elif modules_kind == 'tmod31':
return ModulesSystem(TMod31Impl())
elif modules_kind == 'tmod':
return ModulesSystem(TModImpl())
elif modules_kind == 'tmod32':
return ModulesSystem(TModImpl())
elif modules_kind == 'tmod4':
return ModulesSystem(TMod4Impl())
elif modules_kind == 'lmod':
return ModulesSystem(LModImpl())
elif modules_kind == 'spack':
return ModulesSystem(SpackImpl())
else:
raise ConfigError('unknown module system: %s' % modules_kind)
modules_impl = {
None: NoModImpl,
'nomod': NoModImpl,
'tmod31': TMod31Impl,
'tmod': TModImpl,
'tmod32': TModImpl,
'tmod4': TMod4Impl,
'lmod': LModImpl,
'spack': SpackImpl
}
try:
impl_cls = modules_impl[modules_kind]
except KeyError:
raise ConfigError(f'unknown module system: {modules_kind}')

Check warning on line 124 in reframe/core/modules.py

View check run for this annotation

Codecov / codecov/patch

reframe/core/modules.py#L123-L124

Added lines #L123 - L124 were not covered by tests

impl_cls.validate = validate
return ModulesSystem(impl_cls())

def __init__(self, backend):
self._backend = backend
Expand Down Expand Up @@ -175,7 +176,7 @@

@property
def backend(self):
return(self._backend)
return (self._backend)

def available_modules(self, substr=None):
'''Return a list of available modules that contain ``substr`` in their
Expand Down Expand Up @@ -443,6 +444,7 @@

:meta private:
'''
validate = True

def execute(self, cmd, *args):
'''Execute an arbitrary module command using the modules backend.
Expand Down Expand Up @@ -586,6 +588,12 @@
MIN_VERSION = (3, 2)

def __init__(self):
self._version = None
self._validated = False
if self.validate:
self._do_validate()

def _do_validate(self):
# Try to figure out if we are indeed using the TCL version
try:
completed = osext.run_command('modulecmd -V')
Expand All @@ -610,20 +618,25 @@

if (ver_major, ver_minor) < self.MIN_VERSION:
raise ConfigError(
'unsupported TMod version: %s (required >= %s)' %
(version, self.MIN_VERSION))
f'unsupported TMod version: '
f'{version} (required >= {self.MIN_VERSION})'
)

self._version = version
try:
# Try the Python bindings now
completed = osext.run_command(self.modulecmd())
except OSError as e:
raise ConfigError(
'could not get the Python bindings for TMod: ' % e) from e
f'could not get the Python bindings for TMod: {e}'
) from e

if re.search(r'Unknown shell type', completed.stderr):
raise ConfigError(
'Python is not supported by this TMod installation')
'Python is not supported by this TMod installation'
)

self._validated = True

Check warning on line 639 in reframe/core/modules.py

View check run for this annotation

Codecov / codecov/patch

reframe/core/modules.py#L639

Added line #L639 was not covered by tests

def name(self):
return 'tmod'
Expand All @@ -635,6 +648,9 @@
return ' '.join(['modulecmd', 'python', *args])

def _execute(self, cmd, *args):
if not self._validated:
self._do_validate()

Check warning on line 652 in reframe/core/modules.py

View check run for this annotation

Codecov / codecov/patch

reframe/core/modules.py#L651-L652

Added lines #L651 - L652 were not covered by tests

modulecmd = self.modulecmd(cmd, *args)
completed = osext.run_command(modulecmd)
if re.search(r'\bERROR\b', completed.stderr) is not None:
Expand Down Expand Up @@ -718,14 +734,22 @@
MIN_VERSION = (3, 1)

def __init__(self):
self._version = None
self._command = None
self._validated = False
if self.validate:
self._do_validate()

Check warning on line 741 in reframe/core/modules.py

View check run for this annotation

Codecov / codecov/patch

reframe/core/modules.py#L737-L741

Added lines #L737 - L741 were not covered by tests

def _do_validate(self):
# Try to figure out if we are indeed using the TCL version
try:
modulecmd = os.getenv('MODULESHOME')
modulecmd = os.path.join(modulecmd, 'modulecmd.tcl')
completed = osext.run_command(modulecmd)
except OSError as e:
raise ConfigError(
'could not find a sane TMod31 installation: %s' % e) from e
f'could not find a sane TMod31 installation: {e}'
) from e

version_match = re.search(r'Release Tcl (\S+)', completed.stderr,
re.MULTILINE)
Expand All @@ -743,22 +767,26 @@

if (ver_major, ver_minor) < self.MIN_VERSION:
raise ConfigError(
'unsupported TMod version: %s (required >= %s)' %
(version, self.MIN_VERSION))
f'unsupported TMod version: {version} '
f'(required >= {self.MIN_VERSION})'
)

self._version = version
self._command = '%s python' % modulecmd

self._command = f'{modulecmd} python'

Check warning on line 775 in reframe/core/modules.py

View check run for this annotation

Codecov / codecov/patch

reframe/core/modules.py#L775

Added line #L775 was not covered by tests
try:
# Try the Python bindings now
completed = osext.run_command(self._command)
except OSError as e:
raise ConfigError(
'could not get the Python bindings for TMod31: ' % e) from e
f'could not get the Python bindings for TMod31: {e}'
)

if re.search(r'Unknown shell type', completed.stderr):
raise ConfigError(
'Python is not supported by this TMod installation')
'Python is not supported by this TMod installation'
)

self._validated = True

Check warning on line 789 in reframe/core/modules.py

View check run for this annotation

Codecov / codecov/patch

reframe/core/modules.py#L789

Added line #L789 was not covered by tests

def name(self):
return 'tmod31'
Expand All @@ -767,6 +795,9 @@
return ' '.join([self._command, *args])

def _execute(self, cmd, *args):
if not self._validated:
self._do_validate()

Check warning on line 799 in reframe/core/modules.py

View check run for this annotation

Codecov / codecov/patch

reframe/core/modules.py#L798-L799

Added lines #L798 - L799 were not covered by tests

modulecmd = self.modulecmd(cmd, *args)
completed = osext.run_command(modulecmd)
if re.search(r'\bERROR\b', completed.stderr) is not None:
Expand All @@ -793,14 +824,23 @@
MIN_VERSION = (4, 1)

def __init__(self):
self._version = None
self._extra_module_paths = []
self._validated = False
if self.validate:
self._do_validate()

def _do_validate(self):
try:
completed = osext.run_command(self.modulecmd('-V'), check=True)
except OSError as e:
raise ConfigError(
'could not find a sane TMod4 installation') from e
'could not find a sane TMod4 installation'
) from e
except SpawnedProcessError as e:
raise ConfigError(
'could not get the Python bindings for TMod4') from e
'could not get the Python bindings for TMod4'
) from e

version_match = re.match(r'^Modules Release (\S+)\s+',
completed.stderr)
Expand All @@ -812,15 +852,18 @@
ver_major, ver_minor = [int(v) for v in version.split('.')[:2]]
except ValueError:
raise ConfigError(
'could not parse TMod4 version string: ' + version) from None
f'could not parse TMod4 version string: {version}'
) from None

if (ver_major, ver_minor) < self.MIN_VERSION:
raise ConfigError(
'unsupported TMod4 version: %s (required >= %s)' %
(version, self.MIN_VERSION))
f'unsupported TMod4 version: {version} '
f'(required >= {self.MIN_VERSION})'
)

self._version = version
self._extra_module_paths = []
self._validated = True

Check warning on line 866 in reframe/core/modules.py

View check run for this annotation

Codecov / codecov/patch

reframe/core/modules.py#L866

Added line #L866 was not covered by tests

def name(self):
return 'tmod4'
Expand All @@ -829,6 +872,9 @@
return ' '.join(['modulecmd', 'python', *args])

def _execute(self, cmd, *args):
if not self._validated:
self._do_validate()

Check warning on line 876 in reframe/core/modules.py

View check run for this annotation

Codecov / codecov/patch

reframe/core/modules.py#L875-L876

Added lines #L875 - L876 were not covered by tests

modulecmd = self.modulecmd(cmd, *args)
completed = osext.run_command(modulecmd, check=False)
namespace = {}
Expand Down Expand Up @@ -916,6 +962,13 @@
'''Module system for Lmod (Tcl/Lua).'''

def __init__(self):
self._extra_module_paths = []
self._version = None
self._validated = False
if self.validate:
self._do_validate()

def _do_validate(self):
# Try to figure out if we are indeed using LMOD
self._lmod_cmd = os.getenv('LMOD_CMD')
if self._lmod_cmd is None:
Expand All @@ -925,8 +978,7 @@
try:
completed = osext.run_command(f'{self._lmod_cmd} --version')
except OSError as e:
raise ConfigError(
'could not find a sane Lmod installation: %s' % e)
raise ConfigError(f'could not find a sane Lmod installation: {e}')

Check warning on line 981 in reframe/core/modules.py

View check run for this annotation

Codecov / codecov/patch

reframe/core/modules.py#L981

Added line #L981 was not covered by tests

version_match = re.search(r'.*Version\s*(\S+)', completed.stderr,
re.MULTILINE)
Expand All @@ -939,13 +991,14 @@
completed = osext.run_command(self.modulecmd())
except OSError as e:
raise ConfigError(
'could not get the Python bindings for Lmod: ' % e)
f'could not get the Python bindings for Lmod: {e}'
)

if re.search(r'Unknown shell type', completed.stderr):
raise ConfigError('Python is not supported by '
'this Lmod installation')

self._extra_module_paths = []
self._validated = True

Check warning on line 1001 in reframe/core/modules.py

View check run for this annotation

Codecov / codecov/patch

reframe/core/modules.py#L1001

Added line #L1001 was not covered by tests

def name(self):
return 'lmod'
Expand Down Expand Up @@ -1095,15 +1148,23 @@
'''

def __init__(self):
self._name_format = '{name}/{version}-{hash}'
self._version = None
self._validated = False
if self.validate:
self._do_validate()

def _do_validate(self):
# Try to figure out if we are indeed using the TCL version
try:
completed = osext.run_command('spack -V')
except OSError as e:
raise ConfigError(
'could not find a sane Spack installation') from e
'could not find a sane Spack installation'
) from e

self._version = completed.stdout.strip()
self._name_format = '{name}/{version}-{hash}'
self._validated = True

Check warning on line 1167 in reframe/core/modules.py

View check run for this annotation

Codecov / codecov/patch

reframe/core/modules.py#L1167

Added line #L1167 was not covered by tests

def name(self):
return 'spack'
Expand All @@ -1115,6 +1176,9 @@
return ' '.join(['spack', *args])

def _execute(self, cmd, *args):
if not self._validated:
self._do_validate()

Check warning on line 1180 in reframe/core/modules.py

View check run for this annotation

Codecov / codecov/patch

reframe/core/modules.py#L1179-L1180

Added lines #L1179 - L1180 were not covered by tests

modulecmd = self.modulecmd(cmd, *args)
completed = osext.run_command(modulecmd, check=True)
return completed.stdout
Expand Down
8 changes: 6 additions & 2 deletions reframe/core/systems.py
Original file line number Diff line number Diff line change
Expand Up @@ -461,13 +461,14 @@ class System(jsonext.JSONSerializable):
'''

def __init__(self, name, descr, hostnames, modules_system,
preload_env, prefix, outputdir,
modules_system_validate, preload_env, prefix, outputdir,
resourcesdir, stagedir, partitions):
getlogger().debug(f'Initializing system {name!r}')
self._name = name
self._descr = descr
self._hostnames = hostnames
self._modules_system = ModulesSystem.create(modules_system)
self._modules_system = ModulesSystem.create(modules_system,
modules_system_validate)
self._preload_env = preload_env
self._prefix = prefix
self._outputdir = outputdir
Expand Down Expand Up @@ -581,6 +582,9 @@ def create(cls, site_config):
descr=site_config.get('systems/0/descr'),
hostnames=site_config.get('systems/0/hostnames'),
modules_system=site_config.get('systems/0/modules_system'),
modules_system_validate=site_config.get(
'general/resolve_module_conflicts'
),
preload_env=Environment(
name=f'__rfm_env_{sysname}',
modules=site_config.get('systems/0/modules'),
Expand Down