Skip to content

fix: Correct error reporting in ResolveZone, ResolveNetworkForZone and the LB attachment path - #135

Merged
hrak merged 3 commits into
developfrom
fix/zone_duplicate_error
Sep 15, 2026
Merged

hrak merged 3 commits into
developfrom
fix/zone_duplicate_error

Conversation

@hrak

@hrak hrak commented Sep 15, 2026 •

Copy link
Copy Markdown
Member

Summary

Three small error-handling defects found while surveying pkg/errors usage for #133. Each is independent and in its own commit.

1. ResolveZone appended the same error twice

pkg/cloud/zone.go appended an identical Wrapf of the GetZoneID error to retErr both before and after the metrics call:

retErr = multierror.Append(retErr, errors.Wrapf(err, "could not get Zone ID from %v", zSpec.Name))
c.customMetrics.EvaluateErrorAndIncrementAcsReconciliationErrorCounter(err)
retErr = multierror.Append(retErr, errors.Wrapf(err, "could not get Zone ID from %v", zSpec.Name))

So a zone that could not be resolved by name reported could not get Zone ID from <name> twice in the returned multierror. Now one append, metrics first — matching the GetNetworkByName block directly below it.

No test asserted on the duplication, so nothing had pinned the behaviour.

2. ResolveNetworkForZone counted the wrong branch

The ACS error counter was called in the count != 1 branch of the GetNetworkByID check, where err is nil by construction. EvaluateErrorAndIncrementAcsReconciliationErrorCounter ignores nil, so that call did nothing — while a genuine error from GetNetworkByID returned early without ever being counted.

Moved into the err != nil branch, matching both the GetNetworkByName block at the top of the same function and the GetZoneByID block in ResolveZone: count the ACS error, do not count a well-formed response that simply matched no network.

Effect: real ACS failures on that path now show up in acs_reconciliation_error_count, where previously they were invisible.

3. The LB attachment error was formatted, not wrapped

internal/controllers/cloudstackmachine_controller.go:214:

return ctrl.Result{}, errors.Errorf("failed to reconcile LB attachment: %+v", err)

Two problems:

  • Errorf does not wrap. The original error was flattened into a string, so errors.Is/errors.As could not see through it — a caller wanting to match a sentinel underneath (cloud.ErrNotFound, say) silently could not.
  • %+v on a pkg/errors value renders a full stack trace, so the returned error's message carried that trace into the controller logs on every failed load balancer detach.

pkgerrors.Wrap(err, "failed to reconcile LB attachment") fixes both: same message prefix, cause rendered without a stack trace, and Wrap implements Unwrap (via withStack/withMessage, pkg/errors v0.9.1), so Is/As now traverse the chain.

This was the only production call formatting an error into a new one. The remaining %v cases are panics in envtest setup and one Info log, where there is no error to propagate.

Test plan

  • make build
  • make lint — 0 issues
  • make test — all ginkgo suites plus the internal/controllers envtest suite. pkg/cloud/zone_test.go asserts on the exact multierror text of both resolve paths and still passes, confirming the message shapes are unchanged apart from the removed duplicate

🤖 Generated with Claude Code

hrak and others added 3 commits September 15, 2026 13:03
ResolveZone appended an identical Wrapf of the GetZoneID error to retErr
both before and after the metrics call, so a failure to resolve a zone by
name reported the same "could not get Zone ID from <name>" message twice in
the returned multierror.

Keeps one append and puts the metrics call first, matching the equivalent
block in ResolveNetworkForZone directly below it.

No test asserted on the duplication, so nothing had pinned the behaviour.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
reconcileLBattachments' error was turned into a new error with
errors.Errorf("failed to reconcile LB attachment: %+v", err). Two problems.

Errorf does not wrap, so the original error was flattened into a string and
errors.Is and errors.As could not see through it. Any caller wanting to
match on a sentinel underneath, cloud.ErrNotFound for instance, silently
could not.

%+v on a pkg/errors value renders a full stack trace, so the returned
error's message carried that trace into the controller logs on every failed
load balancer detach.

errors.Wrap fixes both: it keeps the "failed to reconcile LB attachment:"
prefix, renders the cause without a stack trace, and implements Unwrap, so
Is and As now traverse the chain.

This was the only production call formatting an error into a new one. The
remaining %v cases are panics in envtest setup and one Info log, where
there is no error to propagate.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ResolveNetworkForZone called the ACS error counter in the count != 1 branch
of the GetNetworkByID check, where err is nil by construction. The counter
ignores nil, so that call did nothing, while a genuine error from
GetNetworkByID returned early without ever being counted.

Moves the call into the err != nil branch. This matches both the
GetNetworkByName block at the top of the same function and the GetZoneByID
block in ResolveZone: count the ACS error, do not count a well-formed
response that simply did not match one network.

Found while fixing the duplicate append in ResolveZone; same copy-paste
origin.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@hrak
hrak force-pushed the fix/zone_duplicate_error branch from a952c1b to bd2111a Compare September 15, 2026 10:05
@hrak
hrak merged commit 1eb151f into develop Sep 15, 2026
3 checks passed
@hrak
hrak deleted the fix/zone_duplicate_error branch September 15, 2026 10:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants