Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
47e4289
Add stub for test sandbox options
TimoSteuerwaldETAS Jul 20, 2026
0f85f37
First attempt with opencode
TimoSteuerwaldETAS Jul 21, 2026
58aa138
Patch the docker environment
TimoSteuerwaldETAS Jul 21, 2026
f1518e6
Update known_limitations.rst
TimoSteuerwaldETAS Jul 22, 2026
734c4cd
Finish support for setting the working dir
TimoSteuerwaldETAS Jul 22, 2026
b60b14d
Stabilize utility function
TimoSteuerwaldETAS Jul 22, 2026
cd3af39
Fix config mapping tests
TimoSteuerwaldETAS Jul 22, 2026
80d6224
Add config mapping tests to the pipeline
TimoSteuerwaldETAS Jul 23, 2026
0b473cb
Add check for scheduling priority / policy
TimoSteuerwaldETAS Jul 23, 2026
96a282a
Remove not needed debug files
TimoSteuerwaldETAS Jul 23, 2026
9b58c52
Tiny modifications and comment
TimoSteuerwaldETAS Jul 23, 2026
93250f7
Remove double replacement if working_dir is not set
TimoSteuerwaldETAS Jul 23, 2026
af1c888
Fix formatting
TimoSteuerwaldETAS Jul 24, 2026
d2e4381
Revert no longer needed patch for score_itf
TimoSteuerwaldETAS Jul 24, 2026
f8476aa
Test different variations of working_dir presence
TimoSteuerwaldETAS Jul 24, 2026
7395420
Parameterize sandbox_options_process executable
TimoSteuerwaldETAS Jul 24, 2026
2dcac91
Remove exports_files, which has been added by fault
TimoSteuerwaldETAS Jul 24, 2026
af6274a
kill with SIGKILL
TimoSteuerwaldETAS Jul 24, 2026
c4ad106
Test more scheduling params
TimoSteuerwaldETAS Jul 27, 2026
58c475d
Introduce get_working_dir
TimoSteuerwaldETAS Jul 27, 2026
25e857b
Adapt user guide for param working_dir
TimoSteuerwaldETAS Jul 29, 2026
df1651b
Drop not needed empty param
TimoSteuerwaldETAS Jul 29, 2026
9d32c49
Provide path to schema file as build parameter
TimoSteuerwaldETAS Jul 29, 2026
66cac30
Add option is_self_terminating = true to sandbox_options processes
TimoSteuerwaldETAS Jul 29, 2026
c5ce909
Drop switch back to startup
TimoSteuerwaldETAS Jul 29, 2026
c8eee88
Fix formatting
TimoSteuerwaldETAS Jul 29, 2026
27040f5
Add header guard
TimoSteuerwaldETAS Jul 30, 2026
36d60fc
Move verify function to verify_sandbox.hpp
TimoSteuerwaldETAS Jul 30, 2026
e82cb71
Add scheduling priority to process b and add process c
TimoSteuerwaldETAS Jul 30, 2026
c4fd3d9
Be more explicit during shutdown
TimoSteuerwaldETAS Jul 30, 2026
bece241
Fix copyright header
TimoSteuerwaldETAS Jul 30, 2026
7583f91
Assert default working_dir in sandbox_options_process_c
TimoSteuerwaldETAS Jul 31, 2026
b2c97c4
Update to itf 0.5.0
TimoSteuerwaldETAS Jul 31, 2026
d23869a
Rework to have test steps visible in cpp file
TimoSteuerwaldETAS Jul 31, 2026
ea9c716
Add timeout option to prevent warning
TimoSteuerwaldETAS Jul 31, 2026
0769a62
Remove duplication in build file
TimoSteuerwaldETAS Jul 31, 2026
089aa02
Add capability SYS_NICE to devcontainer
TimoSteuerwaldETAS Jul 31, 2026
64bc785
Get rid of explicit file names
TimoSteuerwaldETAS Aug 3, 2026
8b9acb0
Have proxy available at all steps
TimoSteuerwaldETAS Aug 3, 2026
9e44e31
Get rid of outdated env variables
TimoSteuerwaldETAS Aug 3, 2026
50d1321
Rework verification_process incl. debugging output
TimoSteuerwaldETAS Aug 3, 2026
2552a53
Get rid of control_daemon_mock.cpp
TimoSteuerwaldETAS Aug 3, 2026
896e67e
Fix and cleanup test
TimoSteuerwaldETAS Aug 3, 2026
08a36df
Fix documentation
TimoSteuerwaldETAS Aug 3, 2026
1a4c964
Drop Off Run Target
TimoSteuerwaldETAS Aug 3, 2026
21c95c2
Format fix
TimoSteuerwaldETAS Aug 3, 2026
ca35c57
Revert heavy modifications in devcontainer.json
TimoSteuerwaldETAS Aug 3, 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
15 changes: 14 additions & 1 deletion patches/BUILD
Original file line number Diff line number Diff line change
@@ -1 +1,14 @@
exports_files(["hedron_compile_commands_env_vars.patch"])
# *******************************************************************************
# 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(["*.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