Skip to content

Commit 7fff3ca

Browse files
author
Vasileios Karakasis
authored
Merge pull request #1186 from ekouts/feat/json-config-tool
[feat] Add a tool to convert from the old to the new configuration syntax
2 parents 007bc0b + 540bf56 commit 7fff3ca

File tree

8 files changed

+340
-10
lines changed

8 files changed

+340
-10
lines changed

ci-scripts/ci-runner.bash

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,13 +137,15 @@ fi
137137
if [[ $(hostname) =~ tsa ]]; then
138138
# FIXME: Temporary workaround until we have a reframe module on Tsa
139139
module load python
140-
python3 -m venv venv.unittests
141-
source venv.unittests/bin/activate
142-
pip install -r requirements.txt
143140
else
144141
module load reframe
145142
fi
146143

144+
# Always install our requirements
145+
python3 -m venv venv.unittests
146+
source venv.unittests/bin/activate
147+
pip install -r requirements.txt
148+
147149
echo "=============="
148150
echo "Loaded Modules"
149151
echo "=============="

reframe/core/config.py

Lines changed: 151 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,20 @@
44
# SPDX-License-Identifier: BSD-3-Clause
55

66
import collections.abc
7+
import json
8+
import jsonschema
9+
import os
710
import re
11+
import tempfile
812

13+
import reframe
914
import reframe.core.debug as debug
1015
import reframe.core.fields as fields
1116
import reframe.utility as util
1217
import reframe.utility.os_ext as os_ext
1318
import reframe.utility.typecheck as types
14-
from reframe.core.exceptions import (ConfigError, ReframeFatalError)
19+
from reframe.core.exceptions import (ConfigError, ReframeError,
20+
ReframeFatalError)
1521

1622

1723
_settings = None
@@ -222,3 +228,147 @@ def create_env(system, partition, name):
222228
system.add_partition(part)
223229

224230
self._systems[sys_name] = system
231+
232+
233+
def convert_old_config(filename):
234+
old_config = load_settings_from_file(filename)
235+
converted = {
236+
'systems': [],
237+
'environments': [],
238+
'logging': [],
239+
'perf_logging': [],
240+
}
241+
old_systems = old_config.site_configuration['systems'].items()
242+
for sys_name, sys_specs in old_systems:
243+
sys_dict = {'name': sys_name}
244+
sys_dict.update(sys_specs)
245+
246+
# Make variables dictionary into a list of lists
247+
if 'variables' in sys_specs:
248+
sys_dict['variables'] = [
249+
[vname, v] for vname, v in sys_dict['variables'].items()
250+
]
251+
252+
# Make partitions dictionary into a list
253+
if 'partitions' in sys_specs:
254+
sys_dict['partitions'] = []
255+
for pname, p in sys_specs['partitions'].items():
256+
new_p = {'name': pname}
257+
new_p.update(p)
258+
if p['scheduler'] == 'nativeslurm':
259+
new_p['scheduler'] = 'slurm'
260+
new_p['launcher'] = 'srun'
261+
elif p['scheduler'] == 'local':
262+
new_p['scheduler'] = 'local'
263+
new_p['launcher'] = 'local'
264+
else:
265+
sched, launch, *_ = p['scheduler'].split('+')
266+
new_p['scheduler'] = sched
267+
new_p['launcher'] = launch
268+
269+
# Make resources dictionary into a list
270+
if 'resources' in p:
271+
new_p['resources'] = [
272+
{'name': rname, 'options': r}
273+
for rname, r in p['resources'].items()
274+
]
275+
276+
# Make variables dictionary into a list of lists
277+
if 'variables' in p:
278+
new_p['variables'] = [
279+
[vname, v] for vname, v in p['variables'].items()
280+
]
281+
282+
if 'container_platforms' in p:
283+
new_p['container_platforms'] = []
284+
for cname, c in p['container_platforms'].items():
285+
new_c = {'name': cname}
286+
new_c.update(c)
287+
if 'variables' in c:
288+
new_c['variables'] = [
289+
[vn, v] for vn, v in c['variables'].items()
290+
]
291+
292+
new_p['container_platforms'].append(new_c)
293+
294+
sys_dict['partitions'].append(new_p)
295+
296+
converted['systems'].append(sys_dict)
297+
298+
old_environs = old_config.site_configuration['environments'].items()
299+
for env_target, env_entries in old_environs:
300+
for ename, e in env_entries.items():
301+
new_env = {'name': ename}
302+
if env_target != '*':
303+
new_env['target_systems'] = [env_target]
304+
305+
new_env.update(e)
306+
307+
# Convert variables dictionary to a list of lists
308+
if 'variables' in e:
309+
new_env['variables'] = [
310+
[vname, v] for vname, v in e['variables'].items()
311+
]
312+
313+
# Type attribute is not used anymore
314+
if 'type' in new_env:
315+
del new_env['type']
316+
317+
converted['environments'].append(new_env)
318+
319+
if 'modes' in old_config.site_configuration:
320+
converted['modes'] = []
321+
old_modes = old_config.site_configuration['modes'].items()
322+
for target_mode, mode_entries in old_modes:
323+
for mname, m in mode_entries.items():
324+
new_mode = {'name': mname, 'options': m}
325+
if target_mode != '*':
326+
new_mode['target_systems'] = [target_mode]
327+
328+
converted['modes'].append(new_mode)
329+
330+
def update_logging_config(log_name, original_log):
331+
new_handlers = []
332+
for h in original_log['handlers']:
333+
new_h = h
334+
new_h['level'] = h['level'].lower()
335+
new_handlers.append(new_h)
336+
337+
converted[log_name].append(
338+
{
339+
'level': original_log['level'].lower(),
340+
'handlers': new_handlers
341+
}
342+
)
343+
344+
update_logging_config('logging', old_config.logging_config)
345+
update_logging_config('perf_logging', old_config.perf_logging_config)
346+
converted['general'] = [{}]
347+
if hasattr(old_config, 'checks_path'):
348+
converted['general'][0][
349+
'check_search_path'
350+
] = old_config.checks_path
351+
352+
if hasattr(old_config, 'checks_path_recurse'):
353+
converted['general'][0][
354+
'check_search_recursive'
355+
] = old_config.checks_path_recurse
356+
357+
if converted['general'] == [{}]:
358+
del converted['general']
359+
360+
# Validate the converted file
361+
schema_filename = os.path.join(reframe.INSTALL_PREFIX,
362+
'schemas', 'config.json')
363+
364+
# We let the following statements raise, because if they do, that's a BUG
365+
with open(schema_filename) as fp:
366+
schema = json.loads(fp.read())
367+
368+
jsonschema.validate(converted, schema)
369+
with tempfile.NamedTemporaryFile(mode='w', delete=False) as fp:
370+
fp.write(f"#\n# This file was automatically generated "
371+
f"by ReFrame based on '{filename}'.\n#\n\n")
372+
fp.write(f'site_configuration = {util.ppretty(converted)}\n')
373+
374+
return fp.name

reframe/utility/__init__.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,64 @@ def toalphanum(s):
126126
return re.sub(r'\W', '_', s)
127127

128128

129+
def ppretty(value, htchar=' ', lfchar='\n', indent=4, basic_offset=0):
130+
'''Format string of dictionaries, lists and tuples
131+
132+
:arg value: The value to be formatted.
133+
:arg htchar: Horizontal-tab character.
134+
:arg lfchar: Linefeed character.
135+
:arg indent: Number of htchar characters for every indentation level.
136+
:arg basic_offset: Basic offset for the representation, any additional
137+
indentation space is added to the ``basic_offset``.
138+
:returns: a formatted string of the ``value``.
139+
'''
140+
141+
nlch = lfchar + htchar * indent * (basic_offset + 1)
142+
if isinstance(value, tuple):
143+
if value == ():
144+
return '()'
145+
146+
items = [
147+
nlch + ppretty(item, htchar, lfchar, indent, basic_offset + 1)
148+
for item in value
149+
]
150+
return '(%s)' % (','.join(items) + lfchar +
151+
htchar * indent * basic_offset)
152+
elif isinstance(value, list):
153+
if value == []:
154+
return '[]'
155+
156+
items = [
157+
nlch + ppretty(item, htchar, lfchar, indent, basic_offset + 1)
158+
for item in value
159+
]
160+
return '[%s]' % (','.join(items) + lfchar +
161+
htchar * indent * basic_offset)
162+
elif isinstance(value, dict):
163+
if value == {}:
164+
return '{}'
165+
166+
items = [
167+
nlch + repr(key) + ': ' +
168+
ppretty(value[key], htchar, lfchar, indent, basic_offset + 1)
169+
for key in value
170+
]
171+
return '{%s}' % (','.join(items) + lfchar +
172+
htchar * indent * basic_offset)
173+
elif isinstance(value, set):
174+
if value == set():
175+
return 'set()'
176+
177+
items = [
178+
nlch + ppretty(item, htchar, lfchar, indent, basic_offset + 1)
179+
for item in value
180+
]
181+
return '{%s}' % (','.join(items) + lfchar +
182+
htchar * indent * basic_offset)
183+
else:
184+
return repr(value)
185+
186+
129187
class ScopedDict(UserDict):
130188
'''This is a special dict that imposes scopes on its keys.
131189

schemas/config.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@
147147
"descr": {"type": "string"},
148148
"scheduler": {
149149
"type": "string",
150-
"enum": ["local", "pbs", "slurm", "squeue"]
150+
"enum": ["local", "pbs", "slurm", "squeue", "torque"]
151151
},
152152
"launcher": {
153153
"type": "string",
@@ -310,7 +310,7 @@
310310
"modes": {
311311
"type": "array",
312312
"items": {
313-
"type": "object",
313+
"type": "object",
314314
"properties": {
315315
"name": {"type": "string"},
316316
"options": {
@@ -350,13 +350,13 @@
350350
"general/check_search_recursive": "true",
351351
"general/target_systems": ["*"],
352352
"perf_logging/target_systems": ["*"],
353-
"logging/handlers/level": "DEBUG",
353+
"logging/handlers/level": "debug",
354354
"logging/handlers/file/append": false,
355355
"logging/handlers/file/timestamp": false,
356356
"logging/handlers/stream/name": "stdout",
357357
"logging/handlers/syslog/socktype": "udp",
358358
"logging/handlers/syslog/facility": "user",
359-
"logging/level": "INFO",
359+
"logging/level": "info",
360360
"logging/target_systems": ["*"],
361361
"modes/target_systems": ["*"],
362362
"schedulers/job_submit_timeout": 60,

schemas/settings.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868
},
6969
{
7070
'name': 'sys0',
71-
'descr': 'System for testing check dependencies',
71+
'descr': 'System for checking test dependencies',
7272
'hostnames': [r'sys\d+'],
7373
'partitions': [
7474
{

tools/convert_config.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Copyright 2016-2020 Swiss National Supercomputing Centre (CSCS/ETH Zurich)
2+
# ReFrame Project Developers. See the top-level LICENSE file for details.
3+
#
4+
# SPDX-License-Identifier: BSD-3-Clause
5+
6+
import os
7+
import sys
8+
9+
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
10+
11+
import reframe.core.config as config # noqa: F401, F403
12+
13+
14+
if __name__ == '__main__':
15+
try:
16+
old_config = sys.argv[1]
17+
except IndexError:
18+
print(f'{sys.argv[0]}: too few arguments', file=sys.stderr)
19+
print(f'Usage: {sys.argv[0]} OLD_CONFIG_FILE', file=sys.stderr)
20+
sys.exit(1)
21+
22+
try:
23+
new_config = config.convert_old_config(old_config)
24+
except Exception as e:
25+
print(f'{sys.argv[0]}: could not convert file: {e}',
26+
file=sys.stderr)
27+
sys.exit(1)
28+
29+
print(
30+
f"Conversion successful! "
31+
f"Please find the converted file at '{new_config}'."
32+
)

unittests/resources/settings.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ class ReframeSettings:
6666
},
6767
'sys0': {
6868
# System used for dependency checking
69-
'descr': 'System for test dependencies unit tests',
69+
'descr': 'System for checking test dependencies',
7070
'hostnames': [r'sys\d+'],
7171
'partitions': {
7272
'p0': {

0 commit comments

Comments
 (0)