Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
c33cfcb
Add stub for test sandbox options
TimoSteuerwaldETAS Jul 20, 2026
ad31e4a
First attempt with opencode
TimoSteuerwaldETAS Jul 21, 2026
d7ab821
Patch the docker environment
TimoSteuerwaldETAS Jul 21, 2026
f8899a2
Update known_limitations.rst
TimoSteuerwaldETAS Jul 22, 2026
872b274
Finish support for setting the working dir
TimoSteuerwaldETAS Jul 22, 2026
bb15acf
Stabilize utility function
TimoSteuerwaldETAS Jul 22, 2026
9117e31
Fix config mapping tests
TimoSteuerwaldETAS Jul 22, 2026
d4f8e9b
Add config mapping tests to the pipeline
TimoSteuerwaldETAS Jul 23, 2026
4a0421b
Add check for scheduling priority / policy
TimoSteuerwaldETAS Jul 23, 2026
9785173
Remove not needed debug files
TimoSteuerwaldETAS Jul 23, 2026
ea0c3eb
Tiny modifications and comment
TimoSteuerwaldETAS Jul 23, 2026
144a472
Remove double replacement if working_dir is not set
TimoSteuerwaldETAS Jul 23, 2026
d91475c
Fix formatting
TimoSteuerwaldETAS Jul 24, 2026
a632a38
Revert no longer needed patch for score_itf
TimoSteuerwaldETAS Jul 24, 2026
9d0bbcb
Test different variations of working_dir presence
TimoSteuerwaldETAS Jul 24, 2026
0f364a5
Parameterize sandbox_options_process executable
TimoSteuerwaldETAS Jul 24, 2026
aabaacf
Remove exports_files, which has been added by fault
TimoSteuerwaldETAS Jul 24, 2026
78a784e
kill with SIGKILL
TimoSteuerwaldETAS Jul 24, 2026
9f5f0f1
Test more scheduling params
TimoSteuerwaldETAS Jul 27, 2026
a878311
Introduce get_working_dir
TimoSteuerwaldETAS Jul 27, 2026
f5845ee
Adapt user guide for param working_dir
TimoSteuerwaldETAS Jul 29, 2026
b76e7ee
Drop not needed empty param
TimoSteuerwaldETAS Jul 29, 2026
a4d29a9
Provide path to schema file as build parameter
TimoSteuerwaldETAS Jul 29, 2026
f3b90f2
Add option is_self_terminating = true to sandbox_options processes
TimoSteuerwaldETAS Jul 29, 2026
bac65be
Drop switch back to startup
TimoSteuerwaldETAS Jul 29, 2026
e91ebb8
Fix formatting
TimoSteuerwaldETAS Jul 29, 2026
948683d
Add header guard
TimoSteuerwaldETAS Jul 30, 2026
550fdd2
Move verify function to verify_sandbox.hpp
TimoSteuerwaldETAS Jul 30, 2026
e68d955
Add scheduling priority to process b and add process c
TimoSteuerwaldETAS Jul 30, 2026
0fb587c
Be more explicit during shutdown
TimoSteuerwaldETAS Jul 30, 2026
5162157
Fix copyright header
TimoSteuerwaldETAS Jul 30, 2026
b84c046
Assert default working_dir in sandbox_options_process_c
TimoSteuerwaldETAS Jul 31, 2026
d3e845a
Update to itf 0.5.0
TimoSteuerwaldETAS Jul 31, 2026
d345e01
Rework to have test steps visible in cpp file
TimoSteuerwaldETAS Jul 31, 2026
12592d1
Add timeout option to prevent warning
TimoSteuerwaldETAS Jul 31, 2026
120e54d
Remove duplication in build file
TimoSteuerwaldETAS Jul 31, 2026
2cb996f
Add capability SYS_NICE to devcontainer
TimoSteuerwaldETAS Jul 31, 2026
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
2 changes: 1 addition & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,6 @@
},


"runArgs": ["--device=/dev/kvm", "--network", "host"],
"runArgs": ["--device=/dev/kvm", "--network", "host", "--cap-add=SYS_NICE"],
"postCreateCommand": "sudo chmod 666 /dev/kvm && sudo usermod -aG kvm $USER"
}
2 changes: 1 addition & 1 deletion .github/workflows/test_and_docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ jobs:
contents: read
pull-requests: read
with:
bazel-target: 'test --lockfile_mode=error //score/... //tests/... --config=x86_64-linux'
bazel-target: 'test --lockfile_mode=error //score/... //tests/... //scripts/... --config=x86_64-linux'
upload-name: 'bazel-testlogs'
packages: 'fakechroot'
build-docs:
Expand Down
17 changes: 17 additions & 0 deletions patches/BUILD
Original file line number Diff line number Diff line change
@@ -1 +1,18 @@
# *******************************************************************************
# Copyright (c) 2026 Contributors to the Eclipse Foundation
#
# See the NOTICE file(s) distributed with this work for additional
# information regarding copyright ownership.
#
# This program and the accompanying materials are made available under the
# terms of the Apache License Version 2.0 which is available at
# https://www.apache.org/licenses/LICENSE-2.0
#
# SPDX-License-Identifier: Apache-2.0
# *******************************************************************************

exports_files(["hedron_compile_commands_env_vars.patch"])

exports_files(["score_bazel_tools.patch"])

exports_files(["score_bazel_tools_python.patch"])
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@ Component
``run_target`` set to ``fallback_run_target``.
* The ``ready_timeout`` is used as the timeout until process state Running is
reached, even in case the ReadyCondition is ``process_state:Terminated``.
* The parameter ``deployment_config/working_dir`` is currently not supported
and is ignored.


Run target
----------
Expand Down
2 changes: 1 addition & 1 deletion score/launch_manager/docs/user_guide/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ deployment_config (object)
* **bin_dir** (string, optional)
* **Description:** Specifies the absolute filesystem path to the directory where the component's executable is installed.
* **working_dir** (string, optional)
* **Description:** Specifies the directory to be used as the working directory for the component during execution.
* **Description:** Specifies the directory to be used as the working directory for the component during execution. If not defined, the binary's directory (``bin_dir``) is used as the working directory by default.
* **ready_recovery_action** (object, optional)
* **Description:** Specifies the recovery action to execute when the component fails to reach its **Ready State** within the configured ``ready_timeout``. This action is limited to ``restart`` operations.
* **Reference:** This property refers to the ``recovery_action`` reusable type defined in this schema, specifically enforcing the ``restart`` option.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ table Process {
uid: uint (id:4);
gid: uint (id:5);
path: string (id:6);
working_dir: string (id:11);
Comment thread
MaciejKaszynski marked this conversation as resolved.
security_policy_details: string (id:7);
executable_reporting_behavior: ExecutionStateReportingBehaviorEnum (id:8);
startup_config: [ProcessStartupConfig] (id:9);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ void ConfigurationAdapter::fillStartupConfigFromDeployment(
const auto& props = comp.component_properties;

startup.executable_path_ = deploy.bin_dir + "/" + props.binary_name;
startup.working_dir_ = deploy.working_dir;
startup.short_name_ = comp.name;

startup.uid_ = deploy.sandbox.uid;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -521,8 +521,10 @@ bool ConfigurationManager::parseProcessConfigurations(const Process* node)
setSchedulingParameters(*node, *startup_config_node, instance);
// Set executable path from node's path
instance.startup_config_.executable_path_ = getStringFromFlatBuffer(node->path());
instance.startup_config_.working_dir_ = getStringFromFlatBuffer(node->working_dir());
LM_LOG_DEBUG() << "parseProcessConfigurations: Process index:" << instance.process_number_
<< "executable_path_:" << instance.startup_config_.executable_path_;
<< "executable_path_:" << instance.startup_config_.executable_path_
<< " working_dir_:" << instance.startup_config_.working_dir_;

instance.startup_config_.short_name_ = node->identifier() ? node->identifier()->c_str() : "Unknown";
instance.startup_config_.uid_ = node->uid() & 0x7FFFFFFFU;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@
#include <limits.h>
#include <signal.h>

#include "score/mw/launch_manager/common/log.hpp"
#include "score/mw/launch_manager/common/signal_safe_log.hpp"
#include "score/mw/launch_manager/process_group_manager/iprocess.hpp"
#include "score/mw/launch_manager/control/control_client_channel.hpp"
#include "score/mw/launch_manager/common/log.hpp"
#include "score/mw/launch_manager/osal/ipc_comms.hpp"
#include "score/mw/launch_manager/osal/security_policy.hpp"
#include "score/mw/launch_manager/osal/set_affinity.hpp"
#include "score/mw/launch_manager/osal/set_groups.hpp"
#include "score/mw/launch_manager/osal/sys_exit.hpp"
#include "score/mw/launch_manager/process_group_manager/iprocess.hpp"
#include <cerrno>
#include <csignal>
#include <cstdio>
Expand Down Expand Up @@ -106,31 +106,31 @@ void handleComms(score::lcm::internal::osal::ChildProcessConfig& param)
case CommsType::kReporting:
if (-1 == fcntl(IpcCommsSync::sync_fd, F_SETFD, 0))
{
static_cast<void>(signal_safe_log_errno(errno,
"fcntl at line ", __LINE__, " failed"));
static_cast<void>(signal_safe_log_errno(errno, "fcntl at line ", __LINE__, " failed"));
sysexit(EXIT_FAILURE);
}
close(IpcCommsSync::control_client_handler_nudge_fd);
break;
case CommsType::kControlClient:
if (-1 == fcntl(IpcCommsSync::sync_fd, F_SETFD, 0))
{
static_cast<void>(signal_safe_log_errno(errno,
"fcntl at line ", __LINE__, " failed"));
static_cast<void>(signal_safe_log_errno(errno, "fcntl at line ", __LINE__, " failed"));
sysexit(EXIT_FAILURE);
}
if (-1 == fcntl(IpcCommsSync::control_client_handler_nudge_fd, F_SETFD, 0))
{
static_cast<void>(signal_safe_log_errno(errno,
"fcntl at line ", __LINE__, " failed"));
static_cast<void>(signal_safe_log_errno(errno, "fcntl at line ", __LINE__, " failed"));
sysexit(EXIT_FAILURE);
}
break;
case CommsType::kLaunchManager:
// nothing to do here
break;
default:
static_cast<void>(signal_safe_log("at line ", __LINE__, " unknown CommsType ",
static_cast<void>(signal_safe_log(
"at line ",
__LINE__,
" unknown CommsType ",
static_cast<std::int32_t>(param.shared_block->comms_type_)));
sysexit(EXIT_FAILURE);
break;
Expand All @@ -140,23 +140,11 @@ void handleComms(score::lcm::internal::osal::ChildProcessConfig& param)
/// @details The implementation should be async signal safe.
void changeCurrentWorkingDirectory(const score::lcm::internal::osal::OsalConfig& config)
{
// Change current working directory to the same as the executable
constexpr size_t string_size = static_cast<size_t>(PATH_MAX);
// Notice that this next static variable is duplicated by the fork() and so does not need
// any protection by a mutex although at first sight you may think it could need one.
static char path_copy[string_size + 1U] = {0};

if (config.executable_path_.size() >= string_size)
// working_dir_ is set by python configuration generator in lifecycle_config.py, so it should always be valid.
// If not, chdir will fail anyway and we will log an error and exit.
if (-1 == chdir(config.working_dir_.c_str()))
{
static_cast<void>(signal_safe_log("executable path longer than ",
string_size, " chars: ", config.executable_path_));
sysexit(EXIT_FAILURE);
}

if (-1 == chdir(dirname(strncpy(path_copy, config.executable_path_.c_str(), string_size))))
{
static_cast<void>(signal_safe_log_errno(errno,
"chdir(", config.executable_path_, ") failed"));
static_cast<void>(signal_safe_log_errno(errno, "chdir(", config.working_dir_, ") failed."));
sysexit(EXIT_FAILURE);
}
}
Expand All @@ -182,8 +170,8 @@ void changeSecurityPolicy(const score::lcm::internal::osal::OsalConfig& config)
{
if (score::lcm::internal::osal::setSecurityPolicy(config.security_policy_.c_str()) != 0)
{
static_cast<void>(signal_safe_log_errno(errno,
"changeSecurityPolicy(", config.security_policy_, ") failed"));
static_cast<void>(
signal_safe_log_errno(errno, "changeSecurityPolicy(", config.security_policy_, ") failed"));
sysexit(EXIT_FAILURE);
}
}
Expand Down Expand Up @@ -211,8 +199,7 @@ OsalReturnType IProcess::startProcess(ProcessID* pid, IpcCommsP* block, const Os
{
if (access(config->executable_path_.c_str(), X_OK) != 0)
{
static_cast<void>(signal_safe_log(
"File does not exist or is not executable: ", config->executable_path_));
static_cast<void>(signal_safe_log("File does not exist or is not executable: ", config->executable_path_));
return result;
}

Expand Down Expand Up @@ -287,7 +274,8 @@ inline bool IProcess::setupComms(IpcCommsP& block, int& fd, const OsalConfig& co
length += sizeof(ControlClientChannel);
}

static_cast<void>(snprintf(shm_name,
static_cast<void>(snprintf(
shm_name,
static_cast<uint32_t>(score::lcm::internal::ProcessLimits::maxLocalBuffSize),
"/ipc_shared_mem%u",
shm_name_counter++));
Expand Down Expand Up @@ -389,14 +377,22 @@ OsalReturnType IProcess::setSchedulingAndSecurity(const OsalConfig& config)

if (sch_param.sched_priority < sched_get_priority_min(config.scheduling_policy_))
{
static_cast<void>(signal_safe_log("Scheduling priority ", sch_param.sched_priority,
" is below minimum for policy ", config.scheduling_policy_, ", setting to minimum"));
static_cast<void>(signal_safe_log(
"Scheduling priority ",
sch_param.sched_priority,
" is below minimum for policy ",
config.scheduling_policy_,
", setting to minimum"));
sch_param.sched_priority = sched_get_priority_min(config.scheduling_policy_);
}
else if (sch_param.sched_priority > sched_get_priority_max(config.scheduling_policy_))
{
static_cast<void>(signal_safe_log("Scheduling priority ", sch_param.sched_priority,
" is above maximum for policy ", config.scheduling_policy_, ", setting to maximum"));
static_cast<void>(signal_safe_log(
"Scheduling priority ",
sch_param.sched_priority,
" is above maximum for policy ",
config.scheduling_policy_,
", setting to maximum"));
sch_param.sched_priority = sched_get_priority_max(config.scheduling_policy_);
}

Expand Down Expand Up @@ -460,8 +456,8 @@ inline void IProcess::handleChildProcess(ChildProcessConfig& param)
// arguments.", true);
if (-1 == execve(param.config->argv_[0], const_cast<char* const*>(param.config->argv_.data()), param.config->envp_))
{
static_cast<void>(signal_safe_log_errno(errno, "execve failed: Unable to execute the ",
param.config->executable_path_, " app."));
static_cast<void>(signal_safe_log_errno(
errno, "execve failed: Unable to execute the ", param.config->executable_path_, " app."));
sysexit(EXIT_FAILURE);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ struct OsalConfig
int32_t scheduling_policy_; ///< Scheduling policy defined for this process
int32_t scheduling_priority_; ///< Scheduling priority for this process
OsalLimits resource_limits_; ///< Resource limits for this process
std::string working_dir_{}; ///< Working directory for the process
};

/// @brief Struct to hold configuration parameters for the child process.
Expand Down
49 changes: 23 additions & 26 deletions scripts/config_mapping/integration_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,17 @@
)

script_dir = Path(__file__).parent
schema_path = (
script_dir.parent.parent
/ "score"
/ "launch_manager"
/ "daemon"
/ "src"
/ "configuration"
/ "config_schema"
/ "launch_manager.schema.json"
)
tests_dir = script_dir / "tests"
lifecycle_script = script_dir / "lifecycle_config.py"


def run(input_file: Path, test_name: str, compare_files_only=[], exclude_files=[]):
def run(
input_file: Path,
test_name: str,
schema_file: Path,
compare_files_only=[],
exclude_files=[],
):
"""
Execute the mapping script with the given input file and compare the generated output with the expected output.
Input:
Expand Down Expand Up @@ -66,7 +62,7 @@ def run(input_file: Path, test_name: str, compare_files_only=[], exclude_files=[
"-o",
str(actual_output_dir),
"--schema",
str(schema_path),
str(schema_file),
]

try:
Expand Down Expand Up @@ -127,17 +123,18 @@ def compare_files(dir1: Path, dir2: Path, files: list) -> bool:
return True


def test_basic():
def test_basic(schema_file):
"""
Basic Smoketest for generating both launch manager and health monitoring configuration
"""

test_name = "basic_test"
input_file = tests_dir / test_name / "input" / "lm_config.json"

run(input_file, test_name)
run(input_file, test_name, schema_file)


def test_health_config_mapping():
def test_health_config_mapping(schema_file):
"""
Test generation of the health monitoring configuration with
* Different application types
Expand All @@ -147,20 +144,20 @@ def test_health_config_mapping():
test_name = "health_config_test"
input_file = tests_dir / test_name / "input" / "lm_config.json"

run(input_file, test_name, exclude_files=["lm_demo.json"])
run(input_file, test_name, schema_file, exclude_files=["lm_demo.json"])


def test_empty_health_config_mapping():
def test_empty_health_config_mapping(schema_file):
"""
Test generation of the health monitoring configuration with no supervised processes
"""
test_name = "empty_health_config_test"
input_file = tests_dir / test_name / "input" / "lm_config.json"

run(input_file, test_name, exclude_files=["lm_demo.json"])
run(input_file, test_name, schema_file, exclude_files=["lm_demo.json"])


def test_launch_config_mapping():
def test_launch_config_mapping(schema_file):
"""
Test generation of the launch manager configuration with
* Different application types
Expand All @@ -170,20 +167,20 @@ def test_launch_config_mapping():
test_name = "lm_config_test"
input_file = tests_dir / test_name / "input" / "lm_config.json"

run(input_file, test_name, compare_files_only=["lm_demo.json"])
run(input_file, test_name, schema_file, compare_files_only=["lm_demo.json"])


def test_empty_launch_config_mapping():
def test_empty_launch_config_mapping(schema_file):
"""
Test generation of the launch manager configuration with no processes defined
"""
test_name = "empty_lm_config_test"
input_file = tests_dir / test_name / "input" / "lm_config.json"

run(input_file, test_name, compare_files_only=["lm_demo.json"])
run(input_file, test_name, schema_file, compare_files_only=["lm_demo.json"])


def test_custom_validation_failures():
def test_custom_validation_failures(schema_file):
"""
Test that custom validation checks implemented in lifecycle_config.py are correctly identifying invalid configurations.
The input configuration contains the following issues:
Expand All @@ -197,7 +194,7 @@ def test_custom_validation_failures():
input_file = tests_dir / test_name / "input" / "lm_config.json"

try:
run(input_file, test_name)
run(input_file, test_name, schema_file)
raise AssertionError(
"Expected an error due to custom validation failures, but the mapping script executed successfully."
)
Expand All @@ -223,7 +220,7 @@ def test_custom_validation_failures():
)


def test_schema_validation_failures():
def test_schema_validation_failures(schema_file):
"""
Test that schema validation errors are correctly raised when the input configuration does not conform to the defined JSON schema.
The input configuration contains the following issues:
Expand All @@ -233,7 +230,7 @@ def test_schema_validation_failures():
input_file = tests_dir / test_name / "input" / "lm_config.json"

try:
run(input_file, test_name)
run(input_file, test_name, schema_file)
raise AssertionError(
"Expected an error due to schema validation failures, but the mapping script executed successfully."
)
Expand Down
Loading
Loading