diff --git a/score/launch_manager/docs/product_documentation/configuration.rst b/score/launch_manager/docs/product_documentation/configuration.rst index 7ba96cd06f..52e04a77de 100644 --- a/score/launch_manager/docs/product_documentation/configuration.rst +++ b/score/launch_manager/docs/product_documentation/configuration.rst @@ -322,6 +322,10 @@ sandbox.max_cpu_usage (integer, optional) Description: Specifies the maximum CPU usage limit for the component, expressed as a percentage (%) of total CPU capacity. Constraint: Must be greater than 0. +sandbox.affinity_mask (string, optional) + Description: Specifies the affinity mask defining on which CPU cores the component may be scheduled, as a hexadecimal string prefixed with ``0x`` (e.g. ``"0x3"``). Bit 0 corresponds to core 0, bit 1 to core 1, and so on; up to 64 cores are supported. For example, ``"0x5"`` selects cores 0 and 2. If not configured, the component inherits the CPU affinity of the **Launch Manager** itself, i.e. no affinity system call is made on its behalf. + Constraint: Must be a hexadecimal string with the ``0x``/``0X`` prefix, at most 16 hex digits (64 bits), and at least one bit set. + .. _lm_conf_launch_manager_root_properties: Launch manager root properties diff --git a/score/launch_manager/src/daemon/src/configuration/component_config.hpp b/score/launch_manager/src/daemon/src/configuration/component_config.hpp index 9aea85b208..3849c2fa2e 100644 --- a/score/launch_manager/src/daemon/src/configuration/component_config.hpp +++ b/score/launch_manager/src/daemon/src/configuration/component_config.hpp @@ -91,6 +91,7 @@ struct Sandbox std::int32_t scheduling_priority{}; std::optional max_memory_usage; std::optional max_cpu_usage; + std::optional affinity_mask; }; struct DeploymentConfig diff --git a/score/launch_manager/src/daemon/src/configuration/config_schema/launch_manager.schema.json b/score/launch_manager/src/daemon/src/configuration/config_schema/launch_manager.schema.json index 2097d87df4..dad45d62f2 100644 --- a/score/launch_manager/src/daemon/src/configuration/config_schema/launch_manager.schema.json +++ b/score/launch_manager/src/daemon/src/configuration/config_schema/launch_manager.schema.json @@ -319,6 +319,11 @@ "type": "integer", "exclusiveMinimum": 0, "description": "Specifies the maximum CPU usage limit for the component, expressed as a percentage (%) of total CPU capacity." + }, + "affinity_mask": { + "type": "string", + "pattern": "^0[xX](?!0{1,16}$)[0-9a-fA-F]{1,16}$", + "description": "Specifies the affinity mask defining on which CPU cores the process may be scheduled, as a hexadecimal string (e.g. \"0x3\" for cores 0 and 1). Bit 0 corresponds to core 0; up to 64 cores are supported. At least one bit must be set. If not configured, the component inherits the CPU affinity of the **Launch Manager** itself, i.e. no affinity system call is made on its behalf." } }, "additionalProperties": false diff --git a/score/launch_manager/src/daemon/src/configuration/details/flatbuffer_config_loader_UT.cpp b/score/launch_manager/src/daemon/src/configuration/details/flatbuffer_config_loader_UT.cpp index 49a50aff01..1da412b399 100644 --- a/score/launch_manager/src/daemon/src/configuration/details/flatbuffer_config_loader_UT.cpp +++ b/score/launch_manager/src/daemon/src/configuration/details/flatbuffer_config_loader_UT.cpp @@ -519,7 +519,8 @@ TEST_F(FlatbufferConfigLoaderTest, LoadSandbox) fb::SchedulingPolicy::FIFO, 50 /*scheduling_priority*/, 4096 /*max_memory_usage*/, - 80 /*max_cpu_usage*/); + 80 /*max_cpu_usage*/, + 3 /*affinity_mask*/); auto bin_dir = fbb.CreateString("/opt"); auto work_dir = fbb.CreateString("/tmp"); @@ -554,6 +555,41 @@ TEST_F(FlatbufferConfigLoaderTest, LoadSandbox) EXPECT_THAT(sb.max_memory_usage.value(), Eq(4096U)); ASSERT_THAT(sb.max_cpu_usage.has_value(), IsTrue()); EXPECT_THAT(sb.max_cpu_usage.value(), Eq(80U)); + ASSERT_THAT(sb.affinity_mask.has_value(), IsTrue()); + EXPECT_THAT(sb.affinity_mask.value(), Eq(3U)); +} + +TEST_F(FlatbufferConfigLoaderTest, LoadSandboxWithoutAffinityMaskHasNoValue) +{ + RecordProperty("Description", "Sandbox without affinity_mask configured leaves it as std::nullopt."); + + ::flatbuffers::FlatBufferBuilder fbb; + + auto sandbox = buildDefaultSandbox(fbb); + + auto bin_dir = fbb.CreateString("/opt"); + auto work_dir = fbb.CreateString("/tmp"); + auto deploy = fb::CreateDeploymentConfig( + fbb, + 500 /*ready_timeout_ms*/, + 500 /*shutdown_timeout_ms*/, + 0 /*environmental_variables*/, + bin_dir, + work_dir, + 0 /*ready_recovery_action*/, + 0 /*recovery_action*/, + sandbox); + + auto comp = buildDefaultComponent(fbb, "unsandboxed_comp", buildDefaultComponentProperties(fbb), deploy); + auto comps = fbb.CreateVector(std::vector<::flatbuffers::Offset>{comp}); + + auto result = loadBuffer(buildConfigWithComponents(fbb, comps)); + + ASSERT_THAT(result.has_value(), IsTrue()); + ASSERT_THAT(result->components().size(), Eq(1U)); + + const auto& sb = result->components()[0].deployment_config.sandbox; + EXPECT_THAT(sb.affinity_mask.has_value(), IsFalse()); } TEST_F(FlatbufferConfigLoaderTest, LoadComponentAliveSupervision) diff --git a/score/launch_manager/src/daemon/src/configuration/details/flatbuffer_type_converters.cpp b/score/launch_manager/src/daemon/src/configuration/details/flatbuffer_type_converters.cpp index 8a1cbbf888..6021c01860 100644 --- a/score/launch_manager/src/daemon/src/configuration/details/flatbuffer_type_converters.cpp +++ b/score/launch_manager/src/daemon/src/configuration/details/flatbuffer_type_converters.cpp @@ -415,6 +415,7 @@ score::cpp::expected convertSandbox(const fb::San result.scheduling_priority = *scheduling_priority; result.max_memory_usage = optionalScalarValue(fb_sb->max_memory_usage()); result.max_cpu_usage = optionalScalarValue(fb_sb->max_cpu_usage()); + result.affinity_mask = optionalScalarValue(fb_sb->affinity_mask()); return result; } diff --git a/score/launch_manager/src/daemon/src/configuration/details/flatbuffer_type_converters_UT.cpp b/score/launch_manager/src/daemon/src/configuration/details/flatbuffer_type_converters_UT.cpp index 77477cb431..3315fcb77e 100644 --- a/score/launch_manager/src/daemon/src/configuration/details/flatbuffer_type_converters_UT.cpp +++ b/score/launch_manager/src/daemon/src/configuration/details/flatbuffer_type_converters_UT.cpp @@ -661,7 +661,8 @@ TEST_F(ConverterTest, ConvertSandboxValid) fb::SchedulingPolicy::FIFO, 50 /*scheduling_priority*/, 4096 /*max_memory_usage*/, - 80 /*max_cpu_usage*/); + 80 /*max_cpu_usage*/, + 3 /*affinity_mask*/); fbb.Finish(sandbox); const auto* ptr = ::flatbuffers::GetRoot(fbb.GetBufferPointer()); @@ -678,6 +679,21 @@ TEST_F(ConverterTest, ConvertSandboxValid) EXPECT_THAT(result->max_memory_usage.value(), Eq(4096U)); ASSERT_THAT(result->max_cpu_usage.has_value(), IsTrue()); EXPECT_THAT(result->max_cpu_usage.value(), Eq(80U)); + ASSERT_THAT(result->affinity_mask.has_value(), IsTrue()); + EXPECT_THAT(result->affinity_mask.value(), Eq(3U)); +} + +TEST_F(ConverterTest, ConvertSandboxWithoutAffinityMaskHasNoValue) +{ + RecordProperty("Description", "Sandbox without affinity_mask configured leaves it as std::nullopt."); + ::flatbuffers::FlatBufferBuilder fbb; + auto sandbox = buildDefaultSandbox(fbb); + fbb.Finish(sandbox); + const auto* ptr = ::flatbuffers::GetRoot(fbb.GetBufferPointer()); + + auto result = convertSandbox(ptr); + ASSERT_THAT(result.has_value(), IsTrue()); + EXPECT_THAT(result->affinity_mask.has_value(), IsFalse()); } TEST_F(ConverterTest, ConvertSandboxMissingUidReturnsError) diff --git a/score/launch_manager/src/daemon/src/configuration/details/lm_flatcfg.fbs b/score/launch_manager/src/daemon/src/configuration/details/lm_flatcfg.fbs index 9e0a1e0ef8..8da36cb7e4 100644 --- a/score/launch_manager/src/daemon/src/configuration/details/lm_flatcfg.fbs +++ b/score/launch_manager/src/daemon/src/configuration/details/lm_flatcfg.fbs @@ -128,6 +128,8 @@ table Sandbox { max_memory_usage:uint64 = null; // optional // Maximum CPU usage as a percentage of total CPU capacity. max_cpu_usage:uint32 = null; // optional + // Affinity mask defining on which CPU cores the process may be scheduled. + affinity_mask:uint64 = null; // optional } // Deployment configuration for a component. diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/mock_proc_launch_syscalls.hpp b/score/launch_manager/src/daemon/src/process_group_manager/details/mock_proc_launch_syscalls.hpp index 404c4b135a..3d714db01a 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/mock_proc_launch_syscalls.hpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/mock_proc_launch_syscalls.hpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -55,6 +56,7 @@ class SyscallMock MOCK_METHOD(int, chdir, (const char* __path), ()); MOCK_METHOD(int, setrlimit, (int __resource, const struct rlimit* __rlimits), ()); MOCK_METHOD(int, setSecurityPolicy, (const char* policy), ()); + MOCK_METHOD(std::int32_t, setaffinity, (std::uint64_t cpumask), ()); MOCK_METHOD(pid_t, getpid, (), ()); MOCK_METHOD(int, fcntl, (int __fd, int __cmd, void* arg), ()); MOCK_METHOD(int, setgroups, (size_t n, const gid_t* groups), ()); @@ -392,6 +394,11 @@ int setgroups(size_t n, const gid_t* groups) return g_syscall_mock->setgroups(n, groups); } +std::int32_t setaffinity(std::uint64_t cpumask) noexcept(true) +{ + return g_syscall_mock->setaffinity(cpumask); +} + } // namespace score::mw::lifecycle::internal::osal // NOLINTEND diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher.cpp index 5a36e01637..380a5813f0 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher.cpp @@ -342,12 +342,15 @@ OsalReturnType ProcessLauncher::setSchedulingAndSecurity(const configuration::Sa retval = OsalReturnType::kFail; } - // Set core affinity using OS specific functionality in osal - not in new config, skip - // if (-1 == osal::setaffinity(0)) - // { - // static_cast(signal_safe_log_errno(errno, "setaffinity failed")); - // retval = OsalReturnType::kFail; - // } + // Set core affinity using OS specific functionality in osal + if (config.affinity_mask.has_value()) + { + if (-1 == osal::setaffinity(config.affinity_mask.value())) + { + static_cast(signal_safe_log_errno(errno, "setaffinity failed")); + retval = OsalReturnType::kFail; + } + } // Set group ID if (-1 == setgid(config.gid)) diff --git a/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp b/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp index 7cc568f2eb..dec29f0dfa 100644 --- a/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp +++ b/score/launch_manager/src/daemon/src/process_group_manager/details/process_launcher_UT.cpp @@ -519,6 +519,38 @@ TEST_F(SetSchedulingAndSecurityTest, setgroupsFails) EXPECT_THROW(static_cast(process_launcher->startProcess(pid_, sync_, config_)), SysExitException); } +TEST_F(SetSchedulingAndSecurityTest, setaffinityFails) +{ + RecordProperty("Description", "Verify that the forked process exits if setting the CPU affinity fails"); + + config_.deployment_config.sandbox.affinity_mask = 0x1U; + + EXPECT_CALL(*g_syscall_mock, setpgid).WillOnce(Return(0)); + EXPECT_CALL(*g_syscall_mock, sched_setscheduler).WillOnce(Return(0)); + EXPECT_CALL(*g_syscall_mock, setaffinity(0x1U)).WillOnce(SetErrnoAndReturn(EINVAL, -1)); + EXPECT_CALL(*g_syscall_mock, setgid).WillOnce(Return(0)); + EXPECT_CALL(*g_syscall_mock, setuid).WillOnce(Return(0)); + + EXPECT_CALL(*g_syscall_mock, sysexit(EXIT_FAILURE)).WillOnce(Throw(SysExitException{})); + + EXPECT_THROW(static_cast(process_launcher->startProcess(pid_, sync_, config_)), SysExitException); +} + +TEST_F(SetSchedulingAndSecurityTest, setaffinityIgnore) +{ + RecordProperty("Description", "Verify that the CPU affinity is not changed if no affinity mask is configured"); + + config_.deployment_config.sandbox.affinity_mask = std::nullopt; + + EXPECT_CALL(*g_syscall_mock, setpgid).WillOnce(Return(0)); + EXPECT_CALL(*g_syscall_mock, sched_setscheduler).WillOnce(Return(0)); + EXPECT_CALL(*g_syscall_mock, setgid).WillOnce(Return(0)); + EXPECT_CALL(*g_syscall_mock, setuid).WillOnce(Return(0)); + EXPECT_CALL(*g_syscall_mock, setaffinity).Times(0); + + static_cast(process_launcher->startProcess(pid_, sync_, config_)); // No return from forked process +} + TEST_F(StartProcessTest, chdirFails) { RecordProperty("Description", "Verify that the forked process exits if changing the working dir fails"); @@ -635,6 +667,7 @@ TEST_F(StartProcessTest, startProcessChildSuccess) const std::vector sgids = {1, 2, 3}; const std::uint64_t mem_limit = 4096; const std::uint32_t cpu_limit = 500; + const std::uint64_t affinity_mask = 0x8000000000000005U; const std::string security_policy = "security"; const std::vector args_in = {"-c 2", "--argument yes"}; const std::vector expected_launch_args = { @@ -647,6 +680,7 @@ TEST_F(StartProcessTest, startProcessChildSuccess) config_.deployment_config.sandbox.supplementary_group_ids = sgids; config_.deployment_config.sandbox.max_memory_usage = mem_limit; config_.deployment_config.sandbox.max_cpu_usage = cpu_limit; + config_.deployment_config.sandbox.affinity_mask = affinity_mask; config_.deployment_config.sandbox.security_policy = security_policy; config_.deployment_config.environmental_variables.add("environment", "yes"); config_.deployment_config.environmental_variables.add("errors", "no"); @@ -661,6 +695,7 @@ TEST_F(StartProcessTest, startProcessChildSuccess) EXPECT_CALL(*g_syscall_mock, sem_init).Times(2).WillRepeatedly(Return(0)); EXPECT_CALL(*g_syscall_mock, setpgid(0, forked_pid)); EXPECT_CALL(*g_syscall_mock, sched_setscheduler(0, scheduler, _)).WillOnce(Return(0)); + EXPECT_CALL(*g_syscall_mock, setaffinity(affinity_mask)).WillOnce(Return(0)); EXPECT_CALL(*g_syscall_mock, setuid(uid)).WillOnce(Return(0)); EXPECT_CALL(*g_syscall_mock, setgid(gid)).WillOnce(Return(0)); EXPECT_CALL(*g_syscall_mock, setgroups(sgids.size(), CArrayMatches(sgids))).WillOnce(Return(0)); diff --git a/scripts/config_mapping/lifecycle_config.py b/scripts/config_mapping/lifecycle_config.py index 1d5c5d3334..57406b5600 100644 --- a/scripts/config_mapping/lifecycle_config.py +++ b/scripts/config_mapping/lifecycle_config.py @@ -264,6 +264,29 @@ def is_supervised(application_type): } +def parse_affinity_mask(value, component_name): + """Convert the user-facing hex string (e.g. "0x3") into the uint64 the flatbuffer expects.""" + if not isinstance(value, str) or not value.lower().startswith("0x"): + raise ValueError( + f"Component '{component_name}': affinity_mask must be a hexadecimal string " + f'such as "0x3" with the "0x" prefix, but got {value!r}.' + ) + try: + mask = int(value, 16) + except ValueError: + raise ValueError( + f"Component '{component_name}': affinity_mask must be a hexadecimal string " + f'such as "0x3", but got {value!r}.' + ) + + if mask < 1 or mask > 0xFFFFFFFFFFFFFFFF: + raise ValueError( + f"Component '{component_name}': affinity_mask must select at least one core " + f"and at most 64 cores, but got {value!r}." + ) + return mask + + def output_filename(input_filename): stem = Path(input_filename).stem return f"{stem}_gen.json" @@ -340,6 +363,10 @@ def gen_config(output_dir, config, input_filename): sandbox_out["max_memory_usage"] = sandbox["max_memory_usage"] if "max_cpu_usage" in sandbox: sandbox_out["max_cpu_usage"] = sandbox["max_cpu_usage"] + if "affinity_mask" in sandbox: + sandbox_out["affinity_mask"] = parse_affinity_mask( + sandbox["affinity_mask"], component_name + ) deployment = { "ready_timeout_ms": depl_cfg["ready_timeout_ms"], diff --git a/scripts/config_mapping/tests/full_config_test/expected_output/lm_config_gen.json b/scripts/config_mapping/tests/full_config_test/expected_output/lm_config_gen.json index 3a2d0efb04..9490356c18 100644 --- a/scripts/config_mapping/tests/full_config_test/expected_output/lm_config_gen.json +++ b/scripts/config_mapping/tests/full_config_test/expected_output/lm_config_gen.json @@ -42,7 +42,8 @@ "scheduling_policy": "RR", "scheduling_priority": 75, "max_memory_usage": 268435456, - "max_cpu_usage": 50 + "max_cpu_usage": 50, + "affinity_mask": 3 }, "environmental_variables": [ { @@ -108,7 +109,8 @@ "scheduling_policy": "FIFO", "scheduling_priority": 50, "max_memory_usage": 536870912, - "max_cpu_usage": 80 + "max_cpu_usage": 80, + "affinity_mask": 3 }, "environmental_variables": [ { @@ -171,7 +173,8 @@ "scheduling_policy": "FIFO", "scheduling_priority": 50, "max_memory_usage": 536870912, - "max_cpu_usage": 80 + "max_cpu_usage": 80, + "affinity_mask": 3 }, "environmental_variables": [ { @@ -234,7 +237,8 @@ "scheduling_policy": "FIFO", "scheduling_priority": 50, "max_memory_usage": 536870912, - "max_cpu_usage": 80 + "max_cpu_usage": 80, + "affinity_mask": 3 }, "environmental_variables": [ { diff --git a/scripts/config_mapping/tests/full_config_test/input/lm_config.json b/scripts/config_mapping/tests/full_config_test/input/lm_config.json index 11ef2da180..559a00ca4f 100644 --- a/scripts/config_mapping/tests/full_config_test/input/lm_config.json +++ b/scripts/config_mapping/tests/full_config_test/input/lm_config.json @@ -30,7 +30,8 @@ "scheduling_policy": "SCHED_FIFO", "scheduling_priority": 50, "max_memory_usage": 536870912, - "max_cpu_usage": 80 + "max_cpu_usage": 80, + "affinity_mask": "0x3" } }, "component_properties": { diff --git a/scripts/config_mapping/unit_tests.py b/scripts/config_mapping/unit_tests.py index 9762d365a6..3399b36894 100644 --- a/scripts/config_mapping/unit_tests.py +++ b/scripts/config_mapping/unit_tests.py @@ -31,6 +31,7 @@ is_supervised, load_json_file, output_filename, + parse_affinity_mask, preprocess_defaults, schema_validation, score_defaults, @@ -945,7 +946,7 @@ def test_gen_config_watchdog_partial_fields_omitted(tmp_path): def test_gen_config_with_sandbox_limits(tmp_path): - """sandbox max_memory_usage and max_cpu_usage should appear in output when present.""" + """sandbox max_memory_usage, max_cpu_usage and affinity_mask should appear in output when present.""" config = { "schema_version": 1, "components": { @@ -962,6 +963,7 @@ def test_gen_config_with_sandbox_limits(tmp_path): "gid": 1000, "max_memory_usage": 1024, "max_cpu_usage": 50, + "affinity_mask": "0x3", }, }, } @@ -982,6 +984,72 @@ def test_gen_config_with_sandbox_limits(tmp_path): sandbox = output["components"][0]["deployment_config"]["sandbox"] assert sandbox["max_memory_usage"] == 1024 assert sandbox["max_cpu_usage"] == 50 + assert sandbox["affinity_mask"] == 3 + + +def test_gen_config_without_affinity_mask(tmp_path): + """affinity_mask should be absent from the output when not configured.""" + config = { + "schema_version": 1, + "components": { + "app1": { + "component_properties": { + "application_profile": {"application_type": "REPORTING"} + }, + "deployment_config": { + "ready_timeout_ms": 1000, + "shutdown_timeout_ms": 2000, + "bin_dir": "/opt", + "sandbox": { + "uid": 1000, + "gid": 1000, + }, + }, + } + }, + "run_targets": {"Startup": {}}, + "initial_run_target": "Startup", + "fallback_run_target": {}, + "alive_supervision": {}, + "watchdog": {}, + } + gen_config(str(tmp_path), config, "test_input.json") + + with open(tmp_path / "test_input_gen.json") as f: + output = json.load(f) + + sandbox = output["components"][0]["deployment_config"]["sandbox"] + assert "affinity_mask" not in sandbox + + +@pytest.mark.parametrize( + "value,expected", + [ + ("0x3", 3), + ("0X3", 3), + ("0xff", 255), + ("0xFF", 255), + ("0x00000001", 1), + ("0xFFFFFFFFFFFFFFFF", 0xFFFFFFFFFFFFFFFF), + ], +) +def test_parse_affinity_mask_accepts_valid_values(value, expected): + assert parse_affinity_mask(value, "app1") == expected + + +@pytest.mark.parametrize( + "value", + [ + "0x0", + "3", + "0xZZ", + 3, + None, + ], +) +def test_parse_affinity_mask_rejects_invalid_values(value): + with pytest.raises(ValueError): + parse_affinity_mask(value, "app1") def test_gen_config_output_filename_matches_spec(tmp_path): diff --git a/tests/integration/sandbox_options/sandbox_options.json b/tests/integration/sandbox_options/sandbox_options.json index 694170940c..dc7d048eb0 100644 --- a/tests/integration/sandbox_options/sandbox_options.json +++ b/tests/integration/sandbox_options/sandbox_options.json @@ -91,7 +91,8 @@ "--uid=0", "--gid=0", "--scheduling-policy=SCHED_RR", - "--scheduling-priority=10" + "--scheduling-priority=10", + "--affinity=0x1" ], "ready_condition": { "process_state": "Terminated" @@ -106,7 +107,8 @@ "gid": 0, "supplementary_group_ids": [], "scheduling_policy": "SCHED_RR", - "scheduling_priority": 10 + "scheduling_priority": 10, + "affinity_mask": "0x1" } } }, @@ -120,7 +122,8 @@ "process_arguments": [ "--uid=0", "--gid=0", - "--working-dir=/tmp/tests/sandbox_options" + "--working-dir=/tmp/tests/sandbox_options", + "--affinity=0x2" ], "ready_condition": { "process_state": "Terminated" @@ -129,6 +132,9 @@ "deployment_config": { "environmental_variables": { "PROCESSIDENTIFIER": "DefaultPG_app2" + }, + "sandbox": { + "affinity_mask": "0x2" } } }, diff --git a/tests/integration/sandbox_options/sandbox_options.py b/tests/integration/sandbox_options/sandbox_options.py index d42a6d3ab0..fc5855c3ef 100644 --- a/tests/integration/sandbox_options/sandbox_options.py +++ b/tests/integration/sandbox_options/sandbox_options.py @@ -125,6 +125,7 @@ def require_realtime_scheduling(request, target): "comp_req__launch_man__supplementary_groups", "comp_req__launch_man__rc_os_state", "comp_req__launch_man__rt_comp_dep", + "comp_req__launch_man__runmask_support", ], partially_verifies=[ "comp_req__launch_man__process_launch_args", @@ -134,7 +135,7 @@ def require_realtime_scheduling(request, target): ) def test_sandbox_options(target, setup_test, assert_test_results, remote_test_dir): """ - Objective: Verifies the effectiveness of sandbox-options as gid, uid, supplementary groups, and scheduling policy. + Objective: Verifies the effectiveness of sandbox-options as gid, uid, supplementary groups, scheduling policy, and CPU affinity mask. The launch manager starts with an initial Run Target. The control daemon activates the "Running" Run Target (starting the managed process with the sandbox options applied), then transitions back to "Startup", and finally activates "Off". Expected Behaviour: All Run Target transitions complete successfully and all processes report running. diff --git a/tests/integration/sandbox_options/verify_sandbox.hpp b/tests/integration/sandbox_options/verify_sandbox.hpp index ee9a5b0c94..ca9ffa44a1 100644 --- a/tests/integration/sandbox_options/verify_sandbox.hpp +++ b/tests/integration/sandbox_options/verify_sandbox.hpp @@ -175,9 +175,6 @@ inline ::testing::AssertionResult verifyAffinity(const std::uint64_t expected_af constexpr int kCores = 64; #if defined(__QNX__) - // ThreadCtl only offers a combined get-and-set: request a run mask covering every CPU (a - // no-op given the process is already confined by its actual mask) and read back the mask - // that was in effect beforehand, which is the state configured by the launch manager. constexpr int kSize = RMSK_SIZE(kCores); struct { @@ -185,12 +182,8 @@ inline ::testing::AssertionResult verifyAffinity(const std::uint64_t expected_af unsigned runmask[kSize]; unsigned inherit_mask[kSize]; } tm{kSize, {}, {}}; - for (int cpu = 0; cpu < kCores; ++cpu) - { - RMSK_SET(cpu, tm.runmask); - RMSK_SET(cpu, tm.inherit_mask); - } + // Calling ThreadCtl with an empty mask will only retrieve the current run mask without changing it. if (ThreadCtl(_NTO_TCTL_RUNMASK_GET_AND_SET_INHERIT, &tm) != 0) { failures << "Failed to get CPU affinity\n";