A provisioning run reported "3 hosts failed" on a two-host deployment, naming the same
host three times. Two of the three reported failures were tasks that ansible had already
ignored by design. Both defects are in _extract_failure_summary
(autobot-slm-backend/services/ansible_utils.py:103-150).
Defect 1 — ignored failures are counted as failures
A task with ignore_errors: true still prints a full fatal: [host]: FAILED! => {...} line,
followed by ...ignoring. The parser keys only on the fatal: prefix
(ansible_utils.py:129) and never looks for ...ignoring, so a task that succeeded by
design is reported as a failure.
This is not hypothetical — it is the designed first-run path. Both marker reads in
autobot-slm-backend/ansible/roles/_shared/tasks/sync_deletions.yml carry
ignore_errors: true (:105 and :112) precisely because a host with nothing deployed yet
has no marker to read:
.autobot_sync_deletions_commit — this module's own marker (:100-105)
.deployed_commit — the legacy read-only bootstrap, itself guarded by
when: _sd_own_marker is failed (:107-113)
So a first provision always emits two ignored fatal: lines, and the summary always
reports them as failures. The operator is told the deploy failed in two places it was
designed to pass, and the one real failure is buried among them.
Defect 2 — the count is of failure events, labelled as hosts
ansible_utils.py:148-150:
count = len(failures)
noun = "host" if count == 1 else "hosts"
return f"{count} {noun} failed — " + "; ".join(failures)
failures holds one entry per fatal: line, not per host. One host failing three tasks
reports "3 hosts failed". On a two-host deployment that is not just wrong, it is
unreachable — it names more hosts than exist, which is what made the report obviously
suspect.
The existing test cannot catch this. ansible_utils_test.py:79-97
(test_multiple_failures) uses two hosts with exactly one failure each, so failure count and
host count coincide and the conflated variable reads correct. The test encodes the ambiguity
instead of discriminating it.
Why this matters beyond cosmetics
The summary is the operator-facing explanation of a failed provision — the thing #14298 built
so users see a task and a reason instead of exit code 2. When it reports expected-and-ignored
tasks as failures, it inverts that: the noise is indistinguishable from the signal, and the
real failure has to be found by hand in the raw log. That is the same nothing found vs
did not look collapse MEASUREMENT_DISCIPLINE.md exists to prevent, on the output side.
Acceptance criteria
Related
A provisioning run reported "3 hosts failed" on a two-host deployment, naming the same
host three times. Two of the three reported failures were tasks that ansible had already
ignored by design. Both defects are in
_extract_failure_summary(
autobot-slm-backend/services/ansible_utils.py:103-150).Defect 1 — ignored failures are counted as failures
A task with
ignore_errors: truestill prints a fullfatal: [host]: FAILED! => {...}line,followed by
...ignoring. The parser keys only on thefatal:prefix(
ansible_utils.py:129) and never looks for...ignoring, so a task that succeeded bydesign is reported as a failure.
This is not hypothetical — it is the designed first-run path. Both marker reads in
autobot-slm-backend/ansible/roles/_shared/tasks/sync_deletions.ymlcarryignore_errors: true(:105and:112) precisely because a host with nothing deployed yethas no marker to read:
.autobot_sync_deletions_commit— this module's own marker (:100-105).deployed_commit— the legacy read-only bootstrap, itself guarded bywhen: _sd_own_marker is failed(:107-113)So a first provision always emits two ignored
fatal:lines, and the summary alwaysreports them as failures. The operator is told the deploy failed in two places it was
designed to pass, and the one real failure is buried among them.
Defect 2 — the count is of failure events, labelled as hosts
ansible_utils.py:148-150:failuresholds one entry perfatal:line, not per host. One host failing three tasksreports "3 hosts failed". On a two-host deployment that is not just wrong, it is
unreachable — it names more hosts than exist, which is what made the report obviously
suspect.
The existing test cannot catch this.
ansible_utils_test.py:79-97(
test_multiple_failures) uses two hosts with exactly one failure each, so failure count andhost count coincide and the conflated variable reads correct. The test encodes the ambiguity
instead of discriminating it.
Why this matters beyond cosmetics
The summary is the operator-facing explanation of a failed provision — the thing #14298 built
so users see a task and a reason instead of
exit code 2. When it reports expected-and-ignoredtasks as failures, it inverts that: the noise is indistinguishable from the signal, and the
real failure has to be found by hand in the raw log. That is the same nothing found vs
did not look collapse
MEASUREMENT_DISCIPLINE.mdexists to prevent, on the output side.Acceptance criteria
fatal:line that ansible followed with...ignoringis not reported as a failure.reads as N hosts. A count of hosts never exceeds the number of hosts in the run.
cannot fail on), and a test covers an ignored failure being excluded.
fatal:lines were ignored yields no failure summary, so it cannotpresent a successful deploy as a failed one.
...ignoringis bounded and cannot mistake a later task's ignored failurefor this one's.
Related