Skip to content
This repository was archived by the owner on Mar 16, 2026. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "rmltk-templates"]
path = rmltk-templates
url = https://github.com/SimonBin/kgc-challenge-tool-template.git
5 changes: 4 additions & 1 deletion bench_executor/collector.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,10 @@ def __init__(self, case_name: str, results_run_path: str,
system_os_version = 'UNKNOWN'
try:
system_os_name = platform.freedesktop_os_release()['NAME']
system_os_version = platform.freedesktop_os_release()['VERSION']
try:
system_os_version = platform.freedesktop_os_release()['VERSION_ID']
except KeyError:
system_os_version = platform.freedesktop_os_release()['VERSION']
except (OSError, KeyError):
self._logger.warning('Cannot extract Freedesktop OS release data')
system_hostname = platform.node()
Expand Down
42 changes: 35 additions & 7 deletions bench_executor/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ def name(self) -> str:
"""The pretty name of the container"""
return self._name

def run(self, command: str = '', detach=True) -> bool:
def run(self, command: str = '', *, working_dir=None, detach=True, environment=None) -> bool:
"""Run the container.

This is used for containers which are long running to provide services
Expand All @@ -107,6 +107,8 @@ def run(self, command: str = '', detach=True) -> bool:
command : str
The command to execute in the container, optionally and defaults to
no command.
working_dir : str
Set a working directory in the container (optional)
detach : bool
If the container may run in the background, default True.

Expand All @@ -115,11 +117,33 @@ def run(self, command: str = '', detach=True) -> bool:
success : bool
Whether running the container was successfull or not.
"""
e = self._environment
if environment is None:
environment = {}

def merge_env(e1, e2):
r = {}
for key in set(e1.keys()).union(e2.keys()):
if key in e2:
in_e1 = key in e1
is_arr = isinstance(e2[key], list) or (in_e1 and isinstance(e1[key], list))
if in_e1 and (is_arr or key == "JDK_JAVA_OPTIONS"):
if is_arr:
r[key] = [*e1[key], *e2[key]]
else:
r[key] = f'{e1[key]} {e2[key]}'
else:
r[key] = e2[key]
else:
r[key] = e1[key]
if isinstance(r[key], list):
r[key] = ' '.join(r[key])
return r

e = merge_env(self._environment, environment)
v = self._volumes
self._started, self._container_id = \
self._docker.run(self._container_name, command, self._name, detach,
self._ports, NETWORK_NAME, e, v)
self._ports, NETWORK_NAME, e, v, working_dir)

if not self._started:
self._logger.error(f'Starting container "{self._name}" failed!')
Expand Down Expand Up @@ -155,7 +179,7 @@ def exec(self, command: str) -> Tuple[bool, List[str]]:

return False, logs

def run_and_wait_for_log(self, log_line: str, command: str = '') -> bool:
def run_and_wait_for_log(self, log_line: str, command: str = '', *, working_dir=None) -> bool:
"""Run the container and wait for a log line to appear.

This blocks until the container's log contains the `log_line`.
Expand All @@ -167,13 +191,15 @@ def run_and_wait_for_log(self, log_line: str, command: str = '') -> bool:
command : str
The command to execute in the container, optionally and defaults to
no command.
working_dir : str
Set a working directory in the container (optional)

Returns
-------
success : bool
Whether the container exited with status code 0 or not.
"""
if not self.run(command):
if not self.run(command, working_dir=working_dir):
self._logger.error(f'Command "{command}" failed')
return False

Expand Down Expand Up @@ -212,7 +238,7 @@ def run_and_wait_for_log(self, log_line: str, command: str = '') -> bool:
self._logger.error(line)
return False

def run_and_wait_for_exit(self, command: str = '') -> bool:
def run_and_wait_for_exit(self, command: str = '', *, working_dir=None, environment=None) -> bool:
"""Run the container and wait for exit

This blocks until the container exit and gives a status code.
Expand All @@ -222,13 +248,15 @@ def run_and_wait_for_exit(self, command: str = '') -> bool:
command : str
The command to execute in the container, optionally and defaults to
no command.
working_dir : str
Set a working directory in the container (optional)

Returns
-------
success : bool
Whether the container exited with status code 0 or not.
"""
if not self.run(command):
if not self.run(command, working_dir=working_dir, environment=environment):
return False

if self._container_id is None:
Expand Down
4 changes: 4 additions & 0 deletions bench_executor/data/metadata.schema
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
"description": "Short description of the case",
"type": "string"
},
"global_environment": {
"description": "Variables to set in the environment to pass to the containers",
"type": "object"
},
"steps": {
"description": "Short description of the case",
"type": "array",
Expand Down
8 changes: 6 additions & 2 deletions bench_executor/docker.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import json
import subprocess
from time import sleep
from typing import List, Tuple
from typing import List, Tuple, Optional
from bench_executor.logger import Logger


Expand Down Expand Up @@ -145,7 +145,7 @@ def pull(self, image: str) -> bool:

def run(self, image: str, command: str, name: str, detach: bool,
ports: dict, network: str, environment: dict,
volumes: List[str], must_pull: bool = True) -> Tuple[bool, str]:
volumes: List[str], workdir: Optional[str], must_pull: bool = True) -> Tuple[bool, str]:
"""Start a Docker container.

Parameters
Expand All @@ -166,6 +166,8 @@ def run(self, image: str, command: str, name: str, detach: bool,
Environment variables to set.
volumes : List[str]
Volumes to mount on the container from the host.
workdir : str
Working directory for the container.
must_pull: bool
Whether the image should be pulled first, default is True.

Expand Down Expand Up @@ -206,6 +208,8 @@ def run(self, image: str, command: str, name: str, detach: bool,
for volume in volumes:
cmd += f' -v "{volume}"'
cmd += f' --network "{network}"'
if workdir is not None:
cmd += f' --workdir "{workdir}"'
cmd += f' {image} {command}'
self._logger.debug(f'Starting Docker container: {cmd}')
status_code, container_id = subprocess.getstatusoutput(cmd)
Expand Down
13 changes: 9 additions & 4 deletions bench_executor/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class Executor:
"""

def __init__(self, main_directory: str, verbose: bool = False,
progress_cb=_progress_cb):
progress_cb=_progress_cb, metadata_filename=METADATA_FILE):
"""Create an instance of the Executor class.

Parameters
Expand All @@ -51,13 +51,16 @@ def __init__(self, main_directory: str, verbose: bool = False,
process_cb : function
Callback to call when a step is completed of the case. By default,
a dummy callback is provided if the argument is missing.
metadata_filename : str
File name to look for step definitions. By default, metadata.json
"""
self._main_directory = os.path.abspath(main_directory)
self._schema = {}
self._resources: List[Dict[str, Any]] = []
self._class_module_mapping: Dict[str, Any] = {}
self._verbose = verbose
self._progress_cb = progress_cb
self._metadata_filename = metadata_filename
self._logger = Logger(__name__, self._main_directory, self._verbose)

self._init_resources()
Expand Down Expand Up @@ -393,7 +396,8 @@ def run(self, case: dict, interval: float,
module = self._class_module_mapping[step['resource']]
resource = getattr(module, step['resource'])(data_path, CONFIG_DIR,
directory,
self._verbose, False)
self._verbose, False,
environment=data.get('global_environment'))
if hasattr(resource, 'initialization'):
if not resource.initialization():
self._logger.error('Failed to initialize resource '
Expand All @@ -416,7 +420,8 @@ def run(self, case: dict, interval: float,
resource = getattr(module, step['resource'])(data_path, CONFIG_DIR,
directory,
self._verbose,
expect_failure)
expect_failure,
environment=data.get('global_environment'))
active_resources.append(resource)

# Containers may need to start up first before executing a command
Expand Down Expand Up @@ -553,7 +558,7 @@ def list(self) -> list:
for directory in glob(self._main_directory):
for root, dirs, files in os.walk(directory):
for file in files:
if os.path.basename(file) == METADATA_FILE:
if os.path.basename(file) == self._metadata_filename:
path = os.path.join(root, file)
with open(path, 'r') as f:
data = json.load(f)
Expand Down
9 changes: 7 additions & 2 deletions bench_executor/mysql.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
class MySQL(Container):
"""MySQL container for executing SQL queries."""
def __init__(self, data_path: str, config_path: str, directory: str,
verbose: bool, expect_failure: bool = False):
verbose: bool, expect_failure: bool = False, environment=None):
"""Creates an instance of the MySQL class.

Parameters
Expand All @@ -44,6 +44,8 @@ def __init__(self, data_path: str, config_path: str, directory: str,
Enable verbose logs.
expect_failure : bool
If we expect a failure or not.
environment : dict
Additional environment variables to use in the container.
"""
self._data_path = os.path.abspath(data_path)
self._config_path = os.path.abspath(config_path)
Expand All @@ -54,11 +56,14 @@ def __init__(self, data_path: str, config_path: str, directory: str,
os.makedirs(tmp_dir, exist_ok=True)
os.makedirs(os.path.join(self._data_path, 'mysql'), exist_ok=True)

if environment is None:
environment = {}
super().__init__(f'kgconstruct/mysql:v{VERSION}', 'MySQL',
self._logger,
expect_failure=expect_failure,
ports={PORT: PORT},
environment={'MYSQL_ROOT_PASSWORD': 'root',
environment={**environment,
'MYSQL_ROOT_PASSWORD': 'root',
'MYSQL_DATABASE': 'db'},
volumes=[f'{self._data_path}/shared/:/data/shared',
f'{self._config_path}/mysql/'
Expand Down
9 changes: 7 additions & 2 deletions bench_executor/postgresql.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
class PostgreSQL(Container):
"""PostgreSQL container for executing SQL queries"""
def __init__(self, data_path: str, config_path: str, directory: str,
verbose: bool, expect_failure: bool = False):
verbose: bool, expect_failure: bool = False, environment=None):
"""Creates an instance of the PostgreSQL class.

Parameters
Expand All @@ -46,6 +46,8 @@ def __init__(self, data_path: str, config_path: str, directory: str,
Enable verbose logs.
expect_failure : bool
If a failure is expected.
environment : dict
Additional environment variables to use in the container.
"""
self._data_path = os.path.abspath(data_path)
self._config_path = os.path.abspath(config_path)
Expand All @@ -57,10 +59,13 @@ def __init__(self, data_path: str, config_path: str, directory: str,
os.makedirs(os.path.join(self._data_path, 'postgresql'), exist_ok=True)
self._tables: List[str] = []

if environment is None:
environment = {}
super().__init__(f'blindreviewing/postgresql:v{VERSION}', 'PostgreSQL',
self._logger,
ports={PORT: PORT},
environment={'POSTGRES_PASSWORD': PASSWORD,
environment={**environment,
'POSTGRES_PASSWORD': PASSWORD,
'POSTGRES_USER': USER,
'POSTGRES_DB': DB,
'PGPASSWORD': PASSWORD,
Expand Down
2 changes: 1 addition & 1 deletion bench_executor/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
class Query():
"""Execute a query on a SPARQL endpoint."""
def __init__(self, data_path: str, config_path: str, directory: str,
verbose: bool, expect_failure: bool = False):
verbose: bool, expect_failure: bool = False, environment=None):
"""Creates an instance of the Query class.

Parameters
Expand Down
5 changes: 4 additions & 1 deletion bench_executor/rmlmapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class RMLMapper(Container):
"""RMLMapper container for executing R2RML and RML mappings."""

def __init__(self, data_path: str, config_path: str, directory: str,
verbose: bool, expect_failure: bool = False):
verbose: bool, expect_failure: bool = False, environment=None):
"""Creates an instance of the RMLMapper class.

Parameters
Expand All @@ -38,6 +38,8 @@ def __init__(self, data_path: str, config_path: str, directory: str,
Enable verbose logs.
expect_failure : bool
If a failure is expected, default False.
environment : dict
Additional environment variables to use in the container.
"""
self._data_path = os.path.abspath(data_path)
self._config_path = os.path.abspath(config_path)
Expand All @@ -47,6 +49,7 @@ def __init__(self, data_path: str, config_path: str, directory: str,
os.makedirs(os.path.join(self._data_path, 'rmlmapper'), exist_ok=True)
super().__init__(f'kgconstruct/rmlmapper:v{VERSION}', 'RMLMapper',
self._logger, expect_failure=expect_failure,
environment=environment,
volumes=[f'{self._data_path}/rmlmapper:/data',
f'{self._data_path}/shared:/data/shared'])

Expand Down
Loading