Commit cfd3d35
authored
test(haos-e2e): trim cache-save race, compress GHCR qcow2, eval boot snapshot (homeassistant-ai#1428)
* ci(haos-e2e): drop redundant cache save in inaddon lane
homeassistant-ai#1407 made both HAOS lanes share the same actions/cache key for the
12 GB qcow2. Both jobs start in parallel on the same PR push, both
try to actions/cache/save@v5 with the same key, and the loser hits
the cache service's reservation guard:
Failed to save: Unable to reserve cache with key
haos-image-XXXXXXXXXXXXXXXX, another job may be creating this cache.
But not before the loser spends ~25s zstdmt-tar'ing the 12 GB qcow2
just to be told no. Remove the save step from the inaddon lane and
let the external lane own the save — both lanes still restore from
the shared key on the next run.
Observed in run homeassistant-ai#283 (job 77593061250):
11:26:17 cache save started
11:26:42 Failed to save: ... another job may be creating this cache
11:26:43 Cache save failed.
Wall-time win in steady state: ~25s on every cache-miss run for the
inaddon lane. No change to cache contents or hit rates.
* perf(haos-e2e): compress qcow2 in-format before oras push + add tag_suffix dispatch input
The publish workflow currently pushes the post-bake qcow2 raw via
ORAS (``haos-test-image.qcow2:application/octet-stream``), which is
~12 GB on disk and downloads at GHCR's ~32 MB/s single-stream cap →
6m 24s on every cache-miss e2e run (run homeassistant-ai#283 job 77593061250).
The image is dominated by sparse-zeroed space from ``qemu-img resize
32G`` plus addon Docker layers; ``qemu-img convert -c -O qcow2``
re-packs the file with the format's native zlib compression and
typically shrinks it ~3–5x.
The result stays a standard qcow2 (same OCI artifact-type, same
consumer code path). qemu decompresses sectors lazily during VM
I/O when the e2e workflow boots it — no upfront xz-style decompress
step on the pull side.
Workflow changes
----------------
* New ``Compress qcow2 in-format (qemu-img convert -c)`` step before
the artifact upload + GHCR push. Logs ``before / after / ratio`` so
the size win is visible in the workflow output.
* ``workflow_dispatch`` gains a ``tag_suffix`` input (default
``latest``) so perf-iteration branches can publish to a
non-``latest`` moving tag (e.g. ``:17.3-haose2eefficiency``) without
disturbing the master-served image other PRs pull from.
Pull-side validation
--------------------
This commit only changes how the image is *published*; the e2e
workflows still pull ``:HAOS_VERSION-latest`` (which is still the
old uncompressed image) until commit 3 in this series flips them
to the new tag.
The e2e test workflows are not in this workflow file's trigger
``paths`` and are not in this PR's commit yet either, so this push
does not auto-trigger an e2e run that would pull a not-yet-existing
image. The next step is a manual ``gh workflow run
build-haos-test-image.yml --ref perf/haos-e2e-improvements -f
tag_suffix=haose2eefficiency`` to publish the compressed image at
``:17.3-haose2eefficiency``.
* test(haos-e2e): TEMP point both lanes at :17.3-haose2eefficiency (perf measurement)
Temporary scaffolding for PR homeassistant-ai#1428 only. The publish workflow on this
branch (dispatched with tag_suffix=haose2eefficiency) just published
the in-format compressed qcow2 to
``ghcr.io/homeassistant-ai/haos-test-image:17.3-haose2eefficiency`` —
size 5.1 GB down from 12 GB (2.3x ratio, see run 26361263298 step 11).
Pointing both test lanes at the new tag lets this PR's e2e CI
exercise the compressed pull path so we can measure GHCR-pull
wall-time end-to-end. The shared actions/cache entry for the
existing key was deleted before this push so both lanes miss the
restore step and fall through to the GHCR fetch.
Final cleanup commit before this PR is marked ready will revert
both ``tag=`` lines back to ``-latest``. Do not merge in this state.
* revert: restore :17.3-latest tag pointer in HAOS e2e workflows
Reverts the temporary tag pointer added in commit 0132186
("test(haos-e2e): TEMP point both lanes at :17.3-haose2eefficiency").
The compressed-qcow2 measurement is done — both lanes pulled
:17.3-haose2eefficiency on commit 2b's run with GHCR pull dropping
from 5m 39s (commit #1, uncompressed) to 47-58s (compressed). See
runs 26361656754 + 26361656780.
After merge, ``build-haos-test-image.yml``'s next master-push run
republishes ``:17.3-latest`` through the compression step (added
in commit ba97395), so the master-served image gets the same
treatment and every future PR's GHCR-pull path benefits.
The ``:17.3-haose2eefficiency`` GHCR tag itself can be deleted
post-merge (or left as a perf-iteration artifact — it doesn't
hurt anything).
This commit's CI run is expected to be SLOWER than commit 2b's
because it pulls the old uncompressed ``:17.3-latest`` — that's
the validation that the master-served path still works end to
end, not a regression.
* test(haos-e2e): address PR review feedback (correctness + comment accuracy)
Aggregated fixes from three PR review agents on PR homeassistant-ai#1428
(comment-analyzer, silent-failure-hunter, code-reviewer).
Correctness / safety:
* ``build-haos-test-image.yml`` compression step now runs
``qemu-img check`` on the compressed file before consumers
(artifact upload + oras push) see it. Catches torn writes and
refcount-table corruption that ``set -e`` alone misses — a
``qemu-img convert`` killed mid-write can produce an exit-0
truncated file via the shell wrapper.
* Added a 1 GiB sanity floor on the compressed file size: the
baked HAOS image is gigabytes; anything smaller is corruption
``qemu-img check`` didn't catch, and we fail before the moving
tag points at bad bytes.
* Added a ``TAG_SUFFIX`` regex guard
(``^[A-Za-z0-9._-]+$``, the OCI tag character set) in the
``Push image to GHCR`` step. A typo like ``foo/bar``, ``:evil``,
or a trailing space would otherwise surface as a confused
``oras`` error several lines deep.
* Dropped ``-p`` from ``qemu-img convert``: the progress bar
renders as one very long line of carriage-return-overwrites in
non-TTY GHA logs without adding signal the ``before/after/ratio``
echoes don't already give.
Comment accuracy:
* ``build_image.py`` had a stale comment claiming
``qemu-img convert -c`` only shrinks ``~7 GB → ~7 GB`` and adds
9 min — that was from a smaller pre-homeassistant-ai#1379 addon set on a
non-resized qcow2. Replaced with the current state: workflow
step does the compress at publish time, measured at 12 GB
→ 5.1 GB (2.3x) in 6m 15s on publish run 26361263298 step 11.
* ``build-haos-test-image.yml`` compression step comment: fixed
"sectors" → "clusters" (qcow2 unit is the 64 KiB cluster);
reframed the "sparse-zeroed space" line (sparse means
*absent*, not zero-filled — the wire-bytes win comes from
ORAS serialising sparse holes as actual zero bytes, which the
dense compressed output skips); added the "writes re-allocate
uncompressed" trade-off; cited the specific publish + e2e runs
that produced the measured numbers.
* ``haos-e2e-inaddon-tests.yml`` no-cache-save comment: added
one paragraph making the byte-identical-qcow2 invariant
explicit (both lanes consume the same publish-time bake; any
PR-level overrides happen in-VM on the per-worker overlay,
never on the cached base).
* ``tag_suffix`` dispatch input description: previously said
*what* the override is for, now also says *how* — the consumer
edit (``tag=`` lines in the two e2e workflow files) is a
separate manual step, and the allowed character set is named.
* Consolidated the duplicate ``TAG_SUFFIX`` env-var explanation
in the ``Push image to GHCR`` step down to a pointer back at
the input description.
No changes to the productive perf wins from this PR series
(commits 8655bd8 + ba97395): cache-save still removed from
inaddon lane, qcow2 still compressed before oras push, tag_suffix
input still wired through.
* revert: don't touch build_image.py in this PR (would force local build)
Reverts the build_image.py stale-comment fix from 897dfd1. That
change invalidated the e2e workflows' cache key (``git ls-tree``
over ``tests/haos_image_build/``) AND tripped the
``Detect PR-modified bake inputs`` gate that skips GHCR pull and
forces a local image build on cache miss.
Local build then filled the runner's 14 GB SSD before the test
suite could run — the exact failure mode homeassistant-ai#1407 was originally
designed to mitigate. External lane run 26362625939 hit:
System.IO.IOException: No space left on device
The stale comment in build_image.py:1400 about
``qemu-img convert -c`` is still wrong, but addressing it in the
same PR as a cache-key-invalidating side effect isn't worth it.
Will follow up separately if needed; the workflow step's own
comment is now the source of truth for the compression numbers.
* fix(haos-e2e): free disk space before local-build path + re-apply build_image.py comment fix
Re-applies the build_image.py stale-comment fix that was reverted
in 65cba9c, paired with a workflow-level fix for the disk-fill
the reverted attempt exposed.
What hit:
* The original review-fix commit (897dfd1) modified
``tests/haos_image_build/build_image.py`` for a docstring update.
* That path is in the cache-key hash (``git ls-tree -r HEAD
tests/haos_image_build``) AND in the ``Detect PR-modified bake
inputs`` gate's regex, so the change correctly invalidated the
cache key AND triggered ``bake_inputs_changed=true``.
* With GHCR fallback intentionally suppressed on bake-input
changes (would otherwise serve master's stale image), the
workflow fell through to the local-build path.
* Local build then tripped ``No space left on device`` mid-bake
(external lane run 26362625939) — the qcow2 download +
decompress + boot + addon-install + ``cp`` peaks against the
~14 GB usable SSD.
Two fixes, one commit:
1. **Workflow-level disk prune** before the local-build path on
both e2e lanes (``haos-e2e-tests.yml`` and
``haos-e2e-inaddon-tests.yml``). Removes the unused
``/usr/share/dotnet``, ``/usr/share/swift``, ``/opt/ghc``,
``/usr/local/lib/android``, ``/usr/local/.ghcup`` trees (~20
GB combined on a standard ubuntu-22.04 runner) plus
``docker system prune`` plus ``apt-get clean``. Only runs on
the conditions that lead into local build —
``cache-hit != 'true' && ghcr-pull.outcome != 'success'`` —
so cache-hit and GHCR-pull paths are unaffected. Without
this, ANY PR that legitimately changes bake inputs (the
point of the cache-invalidation logic) would trip the same
disk-fill that just took out PR homeassistant-ai#1428.
2. **Re-apply the stale-comment fix** in ``build_image.py``
with a shorter, less narrative version: drops the old
incorrect numbers (``~7 GB → ~7 GB``, ``+9 min``) and
replaces them with a pointer to the workflow step that does
the actual compression (homeassistant-ai#1428, measured ``12 GB → 5.1 GB``
/ ``2.3x``).
The build_image.py change still invalidates the cache key,
forcing this commit's CI runs through the local-build path
again — which now has the disk prune to survive.
* ci(haos-e2e): note that external lane is sole cache writer (sanity-check + nav aid)
Adds a three-line comment on the external lane's ``Save image to
cache`` step cross-referencing the inaddon lane's no-save block.
Together with the matching comment in haos-e2e-inaddon-tests.yml
(also homeassistant-ai#1428), a future maintainer reading either side now sees
the full design: shared cache key (homeassistant-ai#1407), single writer (this
PR).
Also serves as the cache-hit-path sanity-check for homeassistant-ai#1428: this
commit doesn't touch any bake-input path, so cache key
``b11cff145b55bc4b``-or-equivalent stays valid and the e2e
workflows will restore from cache rather than re-running the
local build that the previous commit (2772942) exercised. CI
should land at ~6-7 min per lane vs the local-build path's
~11-12 min, confirming the common cache-hit path is unaffected
by this PR.
---------
Co-authored-by: kingpanther13 <kingpanther13@users.noreply.github.com>1 parent 31eb00e commit cfd3d35
4 files changed
Lines changed: 159 additions & 18 deletions
File tree
- .github/workflows
- tests/haos_image_build
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
21 | 21 | | |
22 | 22 | | |
23 | 23 | | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
24 | 41 | | |
25 | 42 | | |
26 | 43 | | |
| |||
146 | 163 | | |
147 | 164 | | |
148 | 165 | | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
149 | 224 | | |
150 | 225 | | |
151 | | - | |
| 226 | + | |
| 227 | + | |
152 | 228 | | |
153 | 229 | | |
154 | 230 | | |
| |||
166 | 242 | | |
167 | 243 | | |
168 | 244 | | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
169 | 251 | | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
170 | 260 | | |
171 | 261 | | |
172 | | - | |
| 262 | + | |
173 | 263 | | |
174 | 264 | | |
175 | 265 | | |
176 | | - | |
| 266 | + | |
177 | 267 | | |
178 | 268 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
142 | 142 | | |
143 | 143 | | |
144 | 144 | | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
145 | 159 | | |
146 | 160 | | |
147 | 161 | | |
| |||
152 | 166 | | |
153 | 167 | | |
154 | 168 | | |
155 | | - | |
156 | | - | |
157 | | - | |
158 | | - | |
159 | | - | |
160 | | - | |
161 | | - | |
162 | | - | |
163 | | - | |
164 | | - | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
165 | 189 | | |
166 | 190 | | |
167 | 191 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
157 | 157 | | |
158 | 158 | | |
159 | 159 | | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
160 | 183 | | |
161 | 184 | | |
162 | 185 | | |
| |||
172 | 195 | | |
173 | 196 | | |
174 | 197 | | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
175 | 202 | | |
176 | 203 | | |
177 | 204 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1397 | 1397 | | |
1398 | 1398 | | |
1399 | 1399 | | |
1400 | | - | |
1401 | | - | |
1402 | | - | |
1403 | | - | |
1404 | | - | |
| 1400 | + | |
| 1401 | + | |
| 1402 | + | |
| 1403 | + | |
| 1404 | + | |
1405 | 1405 | | |
1406 | 1406 | | |
1407 | 1407 | | |
| |||
0 commit comments