Skip to content

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

Open
sujitdb wants to merge 7 commits intosaltstack:masterfrom
sujitdb:fix/localemod-set-locale-container-fallback
Open

Fix locale.set_locale failing on containers without systemd-localed#68859
sujitdb wants to merge 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.
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