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

[feat] Build configuration incrementally by allowing to combine multiple configuration files #2557

Merged
merged 61 commits into from
Nov 21, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
5b91cfc
Allow multiple config files
ekouts Jul 4, 2022
444842b
Merge branch 'master' of https://github.com/reframe-hpc/reframe into …
ekouts Jul 4, 2022
8d267da
Update runreport schema
ekouts Jul 12, 2022
ab9b98c
Merge branch 'master' of https://github.com/reframe-hpc/reframe into …
ekouts Jul 12, 2022
7fcfdca
Address PR comments
ekouts Jul 25, 2022
eff3c83
Remove unused imports
ekouts Aug 8, 2022
8cd2703
Allow multiple config files from envvar
ekouts Aug 8, 2022
c718a76
Merge branch 'master' of https://github.com/reframe-hpc/reframe into …
ekouts Aug 8, 2022
9ccc45e
Move setting in config directory
ekouts Aug 8, 2022
dc0bd27
Merge branch 'master' of https://github.com/reframe-hpc/reframe into …
ekouts Aug 9, 2022
2a3a96c
Add unittests for multiple configurations
ekouts Aug 17, 2022
6df6f6c
Add RFM_CONFIG_PATH
ekouts Aug 17, 2022
94493dd
Update configuration docs
ekouts Aug 17, 2022
d836159
Merge branch 'master' of https://github.com/reframe-hpc/reframe into …
ekouts Sep 6, 2022
fa7edca
Address PR comments
ekouts Sep 7, 2022
7514fa1
Merge branch 'master' of https://github.com/reframe-hpc/reframe into …
ekouts Sep 7, 2022
4b57c15
Update assertions in test_load_multiple_configs
ekouts Sep 7, 2022
29fe80d
Address PR comments
ekouts Sep 9, 2022
15087b4
Update config file format in setup info
ekouts Sep 9, 2022
0e50123
Update debug message
ekouts Sep 9, 2022
ca66dec
Merge branch 'master' of https://github.com/reframe-hpc/reframe into …
ekouts Sep 9, 2022
99a862b
Simplify string join
ekouts Sep 16, 2022
ca5c5dd
Update docs
vkarak Sep 16, 2022
e2b5f02
Address PR comments
ekouts Sep 26, 2022
8f575be
Merge branch 'master' of https://github.com/reframe-hpc/reframe into …
ekouts Sep 26, 2022
9639344
Add more details for configuration in the manpages
ekouts Sep 26, 2022
f7f7fdc
Merge branch 'feat/configuration_scopes' of github.com:ekouts/reframe…
ekouts Sep 26, 2022
4656b7c
Fix unittests
ekouts Sep 26, 2022
92c6d55
Revert --config-files to --config-file
ekouts Sep 29, 2022
afabf46
Add versionchanged in --config-file
ekouts Sep 29, 2022
6eabfc6
Use RFM_CONFIG_PATH in tutorial
ekouts Oct 4, 2022
c35c223
Merge branch 'master' of https://github.com/reframe-hpc/reframe into …
ekouts Oct 4, 2022
115b4f9
Merge branch 'feat/improve-messages' into feat/configuration_scopes
ekouts Oct 5, 2022
936bb36
Remove file
ekouts Oct 6, 2022
171b1a5
Merge branch 'feat/improve-messages' of github.com:vkarak/reframe int…
ekouts Oct 7, 2022
9a4b68a
Merge branch 'feat/improve-messages' of github.com:vkarak/reframe int…
ekouts Oct 7, 2022
0c01d09
Add remote listings
ekouts Oct 7, 2022
9722262
Merge branch 'feat/improve-messages' of github.com:vkarak/reframe int…
ekouts Oct 8, 2022
c460974
Merge branch 'master' of https://github.com/reframe-hpc/reframe into …
ekouts Oct 8, 2022
eea1968
Fix typo
ekouts Oct 8, 2022
a2e9e8e
Use RFM_CONFIG_FILES and update docs
vkarak Oct 8, 2022
22420c5
Update tutorial
vkarak Oct 8, 2022
4c6756a
Add unit tests to expose corner cases
vkarak Oct 9, 2022
8963310
Merge branch 'master' of https://github.com/reframe-hpc/reframe into …
ekouts Oct 21, 2022
034b867
Validate system and partition names are unique
ekouts Oct 21, 2022
2ecea03
Fix unittests
ekouts Oct 21, 2022
4271968
Merge branch 'feat/configuration_scopes' of github.com:ekouts/reframe…
ekouts Oct 25, 2022
5ea6b7a
Enhance how multiple configuration files are handled
vkarak Nov 5, 2022
2c403c0
Update documentation + cleanup
vkarak Nov 12, 2022
5b8dcc4
Adapt tutorial CI script + CSCS CI config
vkarak Nov 12, 2022
095c64b
Fix build system tutorial failure
vkarak Nov 14, 2022
375836d
Update daint listings
ekouts Nov 15, 2022
157783f
Update tutorial Dockerfile
vkarak Nov 15, 2022
640649c
Revert change in `hello2.py`
vkarak Nov 15, 2022
6cd6784
Add cont platform and resources in daint config
ekouts Nov 17, 2022
7f9743f
Patch tutorial for daint
ekouts Nov 17, 2022
f60456f
Merge branch 'master' into feat/configuration_scopes
vkarak Nov 18, 2022
82f0314
Fix tutorial CI
vkarak Nov 18, 2022
c2ec2a2
Create an extended tutorial config that runs all examples
vkarak Nov 19, 2022
f070ab3
Add resources in mc partition
ekouts Nov 21, 2022
83b751b
Merge branch 'master' into feat/configuration_scopes
ekouts Nov 21, 2022
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
125 changes: 48 additions & 77 deletions reframe/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,9 @@ def _hostname(use_fqdn, use_xthostname):


class _SiteConfig:
def __init__(self, site_config, filename):
self._site_config = copy.deepcopy(site_config)
self._filename = filename
def __init__(self):
self._site_config = None
self._filenames = []
self._subconfigs = {}
self._local_system = None
self._sticky_options = {}
Expand All @@ -106,6 +106,23 @@ def __init__(self, site_config, filename):
f'invalid configuration schema: {schema_filename!r}'
) from e

def add_config(self, config, filename):
self._filenames.append(filename)
nc = copy.deepcopy(config)
if self._site_config is None:
self._site_config = nc
return self

for i in ['systems', 'environments', 'scheduler', 'logging',
'modes', 'general']:
if i in nc:
if i in self._site_config:
self._site_config[i] += nc[i]
else:
self._site_config[i] = nc[i]

return self

def _pick_config(self):
if self._local_system:
return self._subconfigs[self._local_system]
Expand All @@ -114,7 +131,7 @@ def _pick_config(self):

def __repr__(self):
return (f'{type(self).__name__}(site_config={self._site_config!r}, '
f'filename={self._filename!r})')
f'filenames={self._filenames!r})')

def __str__(self):
return json.dumps(self._pick_config(), indent=2)
Expand Down Expand Up @@ -242,25 +259,14 @@ def get(self, option, default=None):
return value

@property
def filename(self):
return self._filename
def filenames(self):
return self._filenames

@property
def subconfig_system(self):
return self._local_system

@classmethod
def create(cls, filename):
_, ext = os.path.splitext(filename)
if ext == '.py':
return cls._create_from_python(filename)
elif ext == '.json':
return cls._create_from_json(filename)
else:
raise ConfigError(f"unknown configuration file type: '{filename}'")

@classmethod
def _create_from_python(cls, filename):
def add_python_config(self, filename):
try:
mod = util.import_module_from_file(filename)
except ImportError as e:
Expand All @@ -284,10 +290,9 @@ def _create_from_python(cls, filename):
f"not a valid Python configuration file: '{filename}'"
)

return _SiteConfig(mod.site_configuration, filename)
return self.add_config(mod.site_configuration, filename)

@classmethod
def _create_from_json(cls, filename):
def add_json_config(self, filename):
with open(filename) as fp:
try:
config = json.loads(fp.read())
Expand All @@ -296,7 +301,7 @@ def _create_from_json(cls, filename):
f"invalid JSON syntax in configuration file '{filename}'"
) from e

return _SiteConfig(config, filename)
self.add_config(config, filename)

def _detect_system(self):
getlogger().debug(
Expand Down Expand Up @@ -325,27 +330,8 @@ def validate(self):
try:
jsonschema.validate(site_config, self._schema)
except jsonschema.ValidationError as e:
raise ConfigError(f"could not validate configuration file: "
f"'{self._filename}'") from e

# Make sure that system and partition names are unique
system_names = set()
for system in self._site_config['systems']:
sysname = system['name']
if sysname in system_names:
raise ConfigError(f"system '{sysname}' already defined")

system_names.add(sysname)
partition_names = set()
for part in system['partitions']:
partname = part['name']
if partname in partition_names:
raise ConfigError(
f"partition '{partname}' already defined "
f"for system '{sysname}'"
)

partition_names.add(partname)
raise ConfigError(f"could not validate configuration files: "
f"'{self._filenames}'") from e

def select_subconfig(self, system_fullname=None,
ignore_resolve_errors=False):
Expand Down Expand Up @@ -457,6 +443,7 @@ def select_subconfig(self, system_fullname=None,


def convert_old_config(filename, newfilename=None):
#TODO fix this to get many files
old_config = util.import_module_from_file(filename).settings
converted = {
'systems': [],
Expand Down Expand Up @@ -632,37 +619,21 @@ def handler_list(handler_config, basedir=None):
return fp.name


def _find_config_file():
# The order of elements is important, since it defines the priority
homedir = os.getenv('HOME')
prefixes = [os.path.join(homedir, '.reframe')] if homedir else []
prefixes += [
reframe.INSTALL_PREFIX,
'/etc/reframe.d'
]
valid_exts = ['py', 'json']
getlogger().debug('Looking for a suitable configuration file')
for d in prefixes:
if not d:
continue

for ext in valid_exts:
filename = os.path.join(d, f'settings.{ext}')
getlogger().debug(f'Trying {filename!r}')
if os.path.exists(filename):
return filename

return None


def load_config(filename=None):
if filename is None:
filename = _find_config_file()
if filename is None:
# Return the generic configuration
getlogger().debug('No configuration found; '
'falling back to a generic one')
return _SiteConfig(settings.site_configuration, '<builtin>')

getlogger().debug(f'Loading configuration file: {filename!r}')
return _SiteConfig.create(filename)

def load_config(filenames=None):
ret = _SiteConfig()
getlogger().debug('Loading the generic configuration')
ret.add_config(settings.site_configuration, '<builtin>')
if filenames:
getlogger().debug(f'Loading configuration files: {filenames!r}')
for filename in filenames:
_, ext = os.path.splitext(filename)
if ext == '.py':
ret.add_python_config(filename)
elif ext == '.json':
ret.add_json_config(filename)
else:
raise ConfigError(f"unknown configuration file type: "
f"'{filename}'")

return ret
2 changes: 1 addition & 1 deletion reframe/core/runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,7 @@ def __init__(self, config_file, sysname=None, options=None):
if config_file is None:
_runtime_context = None
else:
site_config = config.load_config(config_file)
site_config = config.load_config([config_file])
site_config.select_subconfig(sysname, ignore_resolve_errors=True)
for opt, value in options.items():
site_config.add_sticky_option(opt, value)
Expand Down
4 changes: 2 additions & 2 deletions reframe/frontend/ci.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ def _emit_gitlab_pipeline(testcases, child_pipeline_opts):
verbosity = 'v' * config.get('general/0/verbose')

def rfm_command(testcase):
if config.filename != '<builtin>':
config_opt = f'-C {config.filename}'
if config.filenames != ['<builtin>']:
config_opt = f'-C {config.filenames}'
else:
config_opt = ''

Expand Down
17 changes: 10 additions & 7 deletions reframe/frontend/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -496,8 +496,8 @@ def main():

# Miscellaneous options
misc_options.add_argument(
'-C', '--config-file', action='store',
dest='config_file', metavar='FILE',
'-C', '--config-file', action='append', metavar='FILE',
dest='config_file',
help='Set configuration file',
envvar='RFM_CONFIG_FILE'
)
Expand Down Expand Up @@ -688,7 +688,7 @@ def restrict_logging():
# to print pretty messages; logging will be reconfigured by user's
# configuration later
site_config = config.load_config(
os.path.join(reframe.INSTALL_PREFIX, 'reframe/core/settings.py')
[os.path.join(reframe.INSTALL_PREFIX, 'reframe/core/settings.py')]
)
site_config.select_subconfig('generic')
options.update_config(site_config)
Expand Down Expand Up @@ -727,15 +727,18 @@ def restrict_logging():
try:
try:
printer.debug('Loading user configuration')
site_config = config.load_config(options.config_file)
if options.config_file is None:
site_config = config.load_config(None)
else:
site_config = config.load_config(options.config_file)
except warnings.ReframeDeprecationWarning as e:
printer.warning(e)
converted = config.convert_old_config(options.config_file)
printer.warning(
f"configuration file has been converted "
f"to the new syntax here: '{converted}'"
)
site_config = config.load_config(converted)
site_config = config.load_config([converted])

site_config.validate()
site_config.set_autodetect_meth(
Expand Down Expand Up @@ -926,7 +929,7 @@ def print_infoline(param, value):

session_info = {
'cmdline': ' '.join(sys.argv),
'config_file': rt.site_config.filename,
'config_files': rt.site_config.filenames,
'data_version': runreport.DATA_VERSION,
'hostname': socket.gethostname(),
'prefix_output': rt.output_prefix,
Expand All @@ -945,7 +948,7 @@ def print_infoline(param, value):
f"{session_info['user'] or '<unknown>'}@{session_info['hostname']}"
)
print_infoline('working directory', repr(session_info['workdir']))
print_infoline('settings file', f"{session_info['config_file']!r}")
print_infoline('settings files', f"{session_info['config_files']!r}")
print_infoline('check search path',
f"{'(R) ' if loader.recurse else ''}"
f"{':'.join(loader.load_path)!r}")
Expand Down
2 changes: 1 addition & 1 deletion reframe/frontend/runreport.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
# The schema data version
# Major version bumps are expected to break the validation of previous schemas

DATA_VERSION = '2.2'
DATA_VERSION = '3.0'
_SCHEMA = os.path.join(rfm.INSTALL_PREFIX, 'reframe/schemas/runreport.json')


Expand Down
5 changes: 4 additions & 1 deletion reframe/schemas/runreport.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,10 @@
"type": "object",
"properties": {
"cmdline": {"type": "string"},
"config_file": {"type": ["string", "null"]},
"config_files": {
"type": "array",
"items": {"type": "string"}
},
"data_version": {"type": "string"},
"hostname": {"type": "string"},
"num_cases": {"type": "number"},
Expand Down
Loading