* Track installed OS update pending reboot activation
Since #6982 the OS update no longer reboots the host automatically, so the
system keeps running the old version until the user reboots. In that window
the update kept being offered: need_update compares the running OS version,
which only changes on reboot. Requesting the same update again re-downloaded
and reinstalled the full image. Worse, a Supervisor restart in that window
dropped the in-memory REBOOT_REQUIRED issue and canceled the pending update
altogether, because mark_healthy marks the booted slot active on startup,
reverting the primary boot slot set by the rauc install.
Track the installed version awaiting a reboot as version_pending and expose
it in /os/info. A successful install sets it, updating again to that version
is rejected with a hint to reboot, and need_update no longer reports true for
an update that is already installed. On load, the pending state is recovered
from rauc by comparing the primary boot slot (via a new GetPrimary D-Bus
wrapper) with the booted slot, re-creating the REBOOT_REQUIRED issue as well.
mark_healthy now skips marking the booted slot active while an update is
pending.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Split pending update detection condition
Fix pylint R0916 (too-many-boolean-expressions) by splitting the guard in
_detect_pending_update into a slot data validity check and the actual
pending update check.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Report pending OS update as current version in /os/info
The Core update entity derives update availability by comparing version
with version_latest from /os/info, so it keeps offering an update that is
already installed until the system is rebooted. Report an installed update
pending activation as the current version so existing Core releases
reflect update availability correctly. Once Core consumes version_pending,
this can be limited to Core versions predating that support.
The hassos field of the root /info endpoint keeps reporting the running
version, Core only uses it as a HAOS presence check.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The hassos-config service was renamed to haos-config in
home-assistant/operating-system#4740. This change preserved the old name
as an alias, however, Supervisor uses ListUnits that doesn't report the
aliases and then thinks such unit doesn't exist. To fix that, simply
call the unit by its primary name which now varies by the OS version.
Fixes#6942
The existing OS manager update tests only exercise precondition failure
paths (out-of-date supervisor, unhealthy state), which bail out before
the download / RAUC install / cleanup logic ever runs. That left the
finally-clause unlink in OSManager.update() unscanned by blockbuster,
so the blocking Path.unlink() regression fixed in #6863 slipped through
CI and only surfaced on a real device.
Add a happy-path test that stubs _download_raucb to create the bundle
file, drives the RAUC dbus mock to emit a successful Completed signal,
mocks the post-install reboot, and asserts the downloaded bundle is
removed. Reverting the executor wrapping on int_ota.unlink reproduces
the original failure with blockbuster reporting "Blocking call to
os.unlink", confirming the test catches the regression.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Run pytest in parallel with pytest-xdist
CI executes the full pytest suite serially, which currently takes
around 4-5 minutes. Most of that time is spent in fixture setup
(D-Bus session, mock services, CoreSys construction) rather than the
test bodies, but each test pays this setup cost on its own worker.
Add pytest-xdist and run with -n auto --dist=loadfile so tests are
distributed across CPU cores while keeping all tests of a file on the
same worker (preserves locality and keeps progress output readable).
GitHub Actions standard runners ship 4 vCPUs, so -n auto picks 4
workers in CI; locally on 8-core machines it picks 8. This matches
the pattern Home Assistant Core has been running for a long time
(--numprocesses auto --dist=loadfile in core's pytest-full job),
so the configuration is already battle-tested in a sibling project.
On an 8-core machine this cuts the local run from ~280s to ~88s
(~3.2x); on the 4-vCPU CI runner expect roughly a ~2x reduction.
The dbus-daemon and other session-scoped fixtures are spawned per
worker, so there is no shared state. pytest-cov already handles xdist
worker coverage merging via the standard .coverage.* files.
* Poll for resolution state in datadisk signal tests
test_multiple_datadisk_add_remove_signals and
test_disabled_datadisk_add_remove_signals fire UDisks2
InterfacesAdded/InterfacesRemoved signals through the real session
dbus-daemon and then assert that supervisor's signal handler chain
has updated coresys.resolution.issues. Each assertion was preceded
by ``await udisks2_service.ping()`` (only confirms signal delivery)
and ``await asyncio.sleep(0.2)`` to let the chained async tasks
finish.
The 0.2 s margin was effectively only 0.1 s of slack:
DataDisk._udisks2_interface_added itself does
``await asyncio.sleep(0.1)`` internally to wait for
UDisks2._interfaces_added (a sibling subscriber on the same signal)
to finish updating the block-device cache before the check runs.
Under xdist parallelism on a 4-vCPU CI runner that 100 ms cushion
evaporates, and the assertion races the handler.
Bumping the test sleep would just kick the can. Instead, replace
the four ``sleep+assert`` sites with a small polling helper
(tests.common.wait_for) that re-checks the predicate every 10 ms
up to a 5 s deadline. The wait completes the moment the resolution
state matches, so the test stays fast on idle and is robust under
load — and the assertion's failure mode becomes a clear "predicate
did not become true within 5 s" instead of a value mismatch.
The product-code sleep inside _udisks2_interface_added is still a
real smell (handler ordering shouldn't depend on a fixed sleep)
but is left for a separate fix; this commit is scoped to the test.
* Block OS updates when the system is unhealthy
In #6024 we mark a system as unhealthy when multiple OS installations
were found. The idea was to block OS updates in this case. However, it
turns out that the OS update job was not checking the system health
and thus allowed updates even when the system was marked as unhealthy.
This commit adds the `JobCondition.HEALTHY` condition to the OS update
job, ensuring that OS updates are only performed when the system is
healthy.
Users can force an OS update still by using
`ha jobs options --ignore-conditions healthy`.
* Add test for update of unhealthy system
---------
Co-authored-by: Jan Čermák <sairon@sairon.cz>
* Recreate aiohttp ClientSession after DNS plug-in load
Create a temporary ClientSession early in case we need to load version
information from the internet. This doesn't use the final DNS setup
and hence might fail to load in certain situations since we don't have
the fallback mechanims in place yet. But if the DNS container image
is present, we'll continue the setup and load the DNS plug-in. We then
can recreate the ClientSession such that it uses the DNS plug-in.
This works around an issue with aiodns, which today doesn't reload
`resolv.conf` automatically when it changes. This lead to Supervisor
using the initial `resolv.conf` as created by Docker. It meant that
we did not use the DNS plug-in (and its fallback capabilities) in
Supervisor. Also it meant that changes to the DNS setup at runtime
did not propagate to the aiohttp ClientSession (as observed in #5332).
* Mock aiohttp.ClientSession for all tests
Currently in several places pytest actually uses the aiohttp
ClientSession and reaches out to the internet. This is not ideal
for unit tests and should be avoided.
This creates several new fixtures to aid this effort: The `websession`
fixture simply returns a mocked aiohttp.ClientSession, which can be
used whenever a function is tested which needs the global websession.
A separate new fixture to mock the connectivity check named
`supervisor_internet` since this is often used through the Job
decorator which require INTERNET_SYSTEM.
And the `mock_update_data` uses the already existing update json
test data from the fixture directory instead of loading the data
from the internet.
* Log ClientSession nameserver information
When recreating the aiohttp ClientSession, log information what
nameservers exactly are going to be used.
* Refuse ClientSession initialization when API is available
Previous attempts to reinitialize the ClientSession have shown
use of the ClientSession after it was closed due to API requets
being handled in parallel to the reinitialization (see #5851).
Make sure this is not possible by refusing to reinitialize the
ClientSession when the API is available.
* Fix pytests
Also sure we don't create aiohttp ClientSession objects unnecessarily.
* Apply suggestions from code review
Co-authored-by: Jan Čermák <sairon@users.noreply.github.com>
---------
Co-authored-by: Jan Čermák <sairon@users.noreply.github.com>
* Delay inital version fetch until there is connectivity
* Add test
* Only mock get not whole websession object
* drive delayed fetch off of supervisor connectivity not host
* Fix test to not rely on sleep guessing to track tasks
* Use fixture to remove job throttle temporarily
* Use signals to recognize new disks immediately
* Add test for disabled data disk issue
* Add mock of UDisks2 base service to test
* Apply suggestions from code review
* Shutdown manager first to avoid potential race conditions
* Update tests/dbus_service_mocks/udisks2.py
Co-authored-by: Jan Čermák <sairon@users.noreply.github.com>
---------
Co-authored-by: Stefan Agner <stefan@agner.ch>
Co-authored-by: Jan Čermák <sairon@users.noreply.github.com>
* Migrate to Ruff for lint and format
* Fix pylint issues
* DBus property sets into normal awaitable methods
* Fix tests relying on separate tasks in connect
* Fixes from feedback
* Add mount to supported features
* Typo in enable
* Fix places mocking os available without version
* Increase resilence of problematic repeat task test
* Add update freeze option
* Freeze to auto update and plugin condition
* Add tests
* Add supervisor_version evaluation
* OS updates require supervisor up to date
* Run version check during startup
* Return list of possible data disk targets
* fix path
* fix tests
* Add test
* Fix tests
* Add tests
* Add more tests
* Remove debug
* Address comments
* more clear