Skip to content

Fix locale.set_locale failing on containers without systemd-localed#68859

Merged
dwoz merged 7 commits intosaltstack:masterfrom
sujitdb:fix/localemod-set-locale-container-fallback
Mar 29, 2026
Merged

Fix locale.set_locale failing on containers without systemd-localed#68859
dwoz merged 7 commits intosaltstack:masterfrom
sujitdb:fix/localemod-set-locale-container-fallback

Conversation

@sujitdb
Copy link
Copy Markdown
Collaborator

@sujitdb sujitdb commented Mar 27, 2026

Summary

  • _localectl_set() falls back to writing /etc/locale.conf directly when localectl set-locale returns non-zero. On containers where systemd-localed is not running, localectl status (read) works fine by reading the file directly, while localectl set-locale (write) requires D-Bus and fails. After the fallback write, get_locale() immediately sees the updated value via localectl status.
  • _check_systemctl() in the integration test is hardened to catch the full range of D-Bus unavailability messages (Connection refused, Failed to connect to bus, Failed to get D-Bus connection) and handles FileNotFoundError when localectl is absent.

Failing test

FAILED tests/integration/modules/test_localemod.py::LocaleModuleTest::test_set_locale
  AssertionError: False is not true

Affected platforms from the nightly run:

  • Amazon Linux 2023 Arm64 integration zeromq 2
  • Photon OS 5 Arm64 functional zeromq(fips) 2
  • Photon OS 5 functional zeromq 2

Root cause

A container image update changed the D-Bus error message emitted by localectl when systemd-localed is not running. The prior skip guard only matched "No such file or directory" in stderr; the updated containers emit "Connection refused" or similar, so the test ran instead of being skipped. When localectl set-locale failed, _localectl_set() returned False directly with no fallback, causing the assertion failure.

Test plan

  • Re-run test_localemod.py::LocaleModuleTest::test_set_locale on Amazon Linux 2023 Arm64
  • Re-run on Photon OS 5 and Photon OS 5 Arm64 (fips)
  • Verify existing passing platforms are unaffected (the localectl set-locale happy path is unchanged)

Made with Cursor

sujitdb added 2 commits March 27, 2026 15:39
Add hatchling to --only-binary in onedir_dependencies() so pip installs
it from its universal wheel instead of attempting a source build.
When --no-binary :all: is active (Linux builds), pip 25.2 tries to
source-build hatchling but hatchling lists itself as its own PEP 517
build backend, causing pip's build tracker to raise:

  LookupError: hatchling is already being built

Fixes saltstack#68858

Made-with: Cursor
On updated Amazon Linux 2023 and Photon OS 5 containers, localectl
set-locale fails with a non-zero exit code because systemd-localed is
not running (D-Bus write access unavailable), while localectl status
continues to work by reading /etc/locale.conf directly.

_localectl_set() now falls back to writing /etc/locale.conf directly
when localectl set-locale returns non-zero.  Modern systemd's localectl
status reads that file without D-Bus, so a subsequent get_locale() call
immediately reflects the change.

_check_systemctl() in the integration test is hardened to skip the test
for the full range of D-Bus connection error messages (Connection refused,
Failed to connect to bus, Failed to get D-Bus connection) and guards
against FileNotFoundError when localectl is absent.

Fixes test_localemod.py::LocaleModuleTest::test_set_locale on:
  - Amazon Linux 2023 Arm64
  - Photon OS 5 Arm64 (fips)
  - Photon OS 5

Made-with: Cursor
@sujitdb sujitdb requested a review from a team as a code owner March 27, 2026 22:58
@sujitdb sujitdb added the test:full Run the full test suite label Mar 27, 2026
dwoz added 3 commits March 28, 2026 15:47
this had two root causes, both related to Python 3.14's new default
multiprocessing start method (forkserver, via PEP 741):

Root Cause 1: `spawning_platform()` didn't recognize `forkserver`

salt/utils/platform.py only checked for "spawn", not "forkserver". This
caused AttributeError: 'Process' object has no attribute
'_args_for_getstate' because Process.__new__ didn't set up pickling
support for forkserver-spawned children.  Fix: Changed
spawning_platform() to return True for both "spawn" and "forkserver".

Root Cause 2: Circular import when `extmods/utils/platform.py` shadows
stdlib

In Python 3.14, the forkserver itself is a fresh Python process (spawned
via exec). When it creates child processes:
1. It sets sys.path from the parent salt-call process via
   preparation_data
2. The parent's sys.path had extmods/utils/ at index 0 (inserted by
   insert_system_path)
3. In the fresh child, import platform found extmods/utils/platform.py
   (salt's platform util) before stdlib's platform.py
4. extmods/utils/platform.py does from salt.utils.decorators import
   memoize which creates a circular import: • salt.utils.decorators →
   salt.utils.versions → salt.version → import platform → (itself) →
   salt.utils.decorators ♻️

Fix 1 (targeted): Replaced from salt.utils.decorators import memoize as
real_memoize in salt/utils/platform.py with
functools.lru_cache(maxsize=None) — a stdlib-only alternative that
breaks the circular dependency.  Fix 2 (defensive): Changed
insert_system_path() in salt/config/__init__.py from sys.path.insert(0,
...) to sys.path.append(...), so extension module directories never
appear before stdlib paths.
@dwoz dwoz merged commit 08713d0 into saltstack:master Mar 29, 2026
1299 of 1303 checks passed
@welcome
Copy link
Copy Markdown

welcome bot commented Mar 29, 2026

Congratulations on your first PR being merged! 🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

test:full Run the full test suite

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants