Repository navigation
bug(tests): most app suites give up on a HelmRelease before it can record why its install failed #3771
Description
Fourteen Chainsaw suites assert HelmRelease readiness at or below the install timeout of the release they are waiting on, so the assert always expires first and the reason the install failed is never reached. pkg/cmd/server/start.go:91 sets HelmReleaseInstallTimeout: "10m" for every release cozystack-api generates, no deployment passes --helmrelease-install-timeout, and only four ApplicationDefinitions override it (kubernetes at 20m, tenant, kubernetes-nodes and monitoring at 15m), none of which these suites use.
| assert budget | suite |
|---|---|
| 5m | external-dns, foundationdb, kuberture, openbao, qdrant |
| 6m | bucket, clickhouse, etcd, kafka, mongodb, postgres, redis, vminstance |
| 10m | harbor |
The budget shown is the tightest HelmRelease-ready assert in each suite; several carry more than one. mariadb is excluded from the list because its budgets are the subject of a separate change.
The list is worth re-deriving rather than trusting, and the derivation is the point: walk every hack/e2e-chainsaw/*/chainsaw-test.yaml, find each kind: HelmRelease inside an assert:, take the nearest enclosing timeout:, and compare it against that release's floor. The neighbouring list, of the kubectl wait calls on HelmReleases elsewhere in hack/, resists being written down at all: a grep for the call misses the invocations split across lines, and reading for it by eye misses the single-release waits among the --all ones. The table below survives both traps because the asserts it walks are structurally uniform, but a reader adding a suite should re-run the walk rather than extend the rows.
Two suites look like the worst cases and are not cases at all. kubernetes-oidc-customconfig and kubernetes-oidc-system assert at 1m, but both wait on releases generated from the kubernetes ApplicationDefinition, which sets release.cozystack.io/helm-install-disable-wait: "true" (packages/system/kubernetes-rd/cozyrds/kubernetes.yaml:28). With the wait disabled the release goes Ready as soon as helm has applied, so 1m is a budget for an apply and is fine. kubernetes-nodes is the only other definition that disables it. Anyone sorting this list by budget will reach for those two first, which is why they are named here rather than left out.
harbor needs its own note, in the opposite direction: its parent release is not generated by cozystack-api but rendered by the app chart, which sets spec.timeout: 15m (packages/apps/harbor/templates/harbor.yaml:91). Its 10m assert is therefore five minutes under its real floor, not level with it as a comparison against the 600s default would suggest.
What this costs
When an install genuinely fails, Chainsaw reports its own timeout against a release that is still mid-install. The release records why the install failed when its own timeout fires, which is after the assert has already given up. The suite says the HelmRelease did not become Ready in six minutes, which is true and carries no information about why.
This is not a flake. These suites do not go red more often than they should; they go red just as often and say less when they do. Nothing here changes how many runs fail, only what a failed run can tell you.
I checked packages/apps/{clickhouse,redis,mongodb,postgres,foundationdb}/templates/ for a startupProbe and found none, so for those five there is no first-boot budget hiding under the wait either. I have not extended that check to the other nine, so whether any of them also needs headroom for its own bootstrap is open.
Why the ordering matters at all
helm-controller leaves spec.waitStrategy at its default of poller, so the install waits through kstatus, which falls through to a Ready-condition fallback for kinds it has no rules for and maps Ready=False onto InProgress. Any of these releases that renders a custom resource publishing a Ready condition therefore keeps the install running until that resource is healthy or the install's own budget is spent. The assert in front of it is sampling a release that is still working, and the only thing it can report is that the release had not finished.
Suggested shape
Whatever budget these asserts end up with should sit above the release's install timeout rather than at or below it, so a failed install has recorded its reason before the assert gives up.
One piece of prose has to move with the numbers. hack/e2e-chainsaw/mariadb/chainsaw-test.yaml:25 justifies its own budget as "11m rather than the usual 5-6m". That is descriptive of the table above and stops being true the moment this list is worked through, at which point it reads as an endorsement of the band it was contrasting against. Whoever raises these budgets owns that sentence.
The general form has two floors and a wait has to beat the larger. The first is the app's own first boot, which for a chart that sets startupProbe is initialDelaySeconds + (failureThreshold - 1) * periodSeconds and for a chart that does not is whatever its operator needs. The second is the install timeout, which is the binding floor whenever the first is smaller, and which is not one number: it is 600s by default, 900s or 1200s where an ApplicationDefinition overrides it, the chart's own spec.timeout for a release the chart renders, and absent entirely where the wait is disabled. Deriving it from the tree rather than pinning a literal is what keeps the relation true when either side moves; hack/mariadb-first-boot-waits.bats does that for one suite and is the shape to copy.
Activity
lexfrei commentedon Sep 22, 2026
The backup examples have the same problem, and this issue does not cover them yet. The round-trip suites run
examples/backups/*/run-all.sh, and itswait_hr_readycalls sit under the same install timeout as the Chainsaw asserts in the table.Line numbers are on
origin/mainata75919087:examples/backups/mariadb/run-all.sh:52,mongodb/run-all.sh:47,postgres/run-all.sh:41,rabbitmq/run-all.sh:46,redis/run-all.sh:44mongodb/run-all.sh:78and:105rabbitmq/run-all.sh:133and:179postgres/run-all.sh:126and:170, already in bug(tests): the postgres backup example's HelmRelease waits are on the same kstatus path with nothing checking the budget #3748 under their old line numbersThe floor is the one this issue already uses. cozystack-api gives every release it generates a 10m install timeout (
pkg/cmd/server/start.go:91). None ofbucket-rd,mongodb-rd,rabbitmq-rdorpostgres-rdsetshelm-install-timeoutorhelm-install-disable-wait. So each of these waits gives up four or five minutes before the release can record why the install failed. Every copy ofwait_hr_readytakes the budget as its second argument, so the fix is a number at each call site.The mariadb example's own mariadb waits (
mariadb/run-all.sh:139and:174) are not listed, because #3769 raises them to 660s. Its bucket wait at:52still needs the fix.I left kafka out on purpose.
examples/backups/kafka-metadata/run-all.sh:36and:113wait 300s, butpackages/system/kafka-rd/cozyrds/kafka.yaml:15setshelm-install-timeout: "7h"to cover the ZooKeeper to KRaft drain Job. No wait can get above that inside a CI job capped at 215 minutes. That needs its own decision, and it is the same one the kafka Chainsaw suite in the table needs.lexfrei commentedon Sep 23, 2026
Some waits under the same floor are missing from both the table and my earlier comment. Line numbers are on
origin/mainat89da4a121.The table also misses these suites. Each one asserts HelmRelease readiness at 6m on a release that cozystack-api generates, and none of
opensearch-rd,rabbitmq-rdorvalkey-rdsetshelm-install-timeoutorhelm-install-disable-wait:hack/e2e-chainsaw/opensearch/chainsaw-test.yaml:128hack/e2e-chainsaw/rabbitmq/chainsaw-test.yaml:27hack/e2e-chainsaw/valkey/chainsaw-test.yaml:25In the backup examples I missed clickhouse. It calls
wait_hr_readyfrom its numbered step scripts, not fromrun-all.sh:examples/backups/clickhouse/03-create-bucket.sh:30examples/backups/clickhouse/04-create-clickhouse.sh:57and07-restore-to-copy.sh:70Part of the example list is now handled elsewhere. #3769 raises the database waits in the clickhouse, mongodb and rabbitmq examples to 660s, same as mariadb, and grows each round-trip op by what those waits gained. After it lands, what stays under the floor in the examples is the bucket waits (clickhouse included) and the postgres waits already tracked in #3748. kafka stays out for the reason in my earlier comment.
Both lists are easy to get wrong by hand, so here is how I built them. The loop prints every HelmRelease assert with its budget, and the grep prints every example wait with a literal budget:
The loop also prints asserts that expect
Readyto beFalse, like the rejected-upgrade check in the redis suite. Those are not readiness waits, skip them. Each remaining budget has to beat its release's own floor: 600s for a generated release unless its ApplicationDefinition overrides it, or the chart'sspec.timeoutfor a release the chart renders itself.lexfrei commentedon Sep 23, 2026
A refreshed enumeration against main at 8d37e0b is in #4404, which I have closed as a duplicate of this issue. It lists 31 HelmRelease-ready asserts in 17 suites at or under their floor, the harbor pair (one on its 10m floor, one 5m under the chart's own 15m), the backup walkthrough waits, and a separate diagnostic gap in the redis and kafka-metadata
wait_hr_readyhelpers. #3769 has since merged: both mariadb asserts are at 11m, and the mariadb, mongodb, rabbitmq and clickhouse walkthrough database waits are at 660s with their Chainsaw ops grown to match. Everything else in that list is unchanged.test(e2e): let HR-Ready waits outlast the install timeout of the rele…