Skip to content

Add internal/apistatus package with typed terminal errors#325

Open
felix-kaestner wants to merge 4 commits into
mainfrom
feat/unsupported-fields
Open

Add internal/apistatus package with typed terminal errors#325
felix-kaestner wants to merge 4 commits into
mainfrom
feat/unsupported-fields

Conversation

@felix-kaestner
Copy link
Copy Markdown
Contributor

@felix-kaestner felix-kaestner commented Apr 27, 2026

Introduces internal/apistatus, a package that gives provider implementations a structured way to return non-retryable errors to controllers without coupling directly to reconcile.TerminalError.

The package defines a Code enum (CodeInvalidArgument, CodeUnsupportedField, CodeFailedPrecondition) with String() and Valid() methods, a StatusError struct carrying a Code, an optional Message, and optional FieldViolations, and constructor functions for each code. StatusError implements Is() so that errors.Is can be used for type detection across error chains. WrapTerminalError wraps any error containing a *StatusError as a reconcile.TerminalError, leaving all other errors unchanged.

conditions.FromError is extended to detect *StatusError before the existing gRPC status path, mapping the code to the condition Reason and the formatted message to the condition Message field.

The stub internal/controllerutil package, which this supersedes, has been removed as no code imported it.

@felix-kaestner felix-kaestner requested a review from a team as a code owner April 27, 2026 07:57
@felix-kaestner felix-kaestner changed the title Add internal/apistatus package with typed terminal errors Add internal/apistatus package with typed terminal errors Apr 27, 2026
@hardikdr hardikdr added the area/switch-automation Automation processes for network switch management and operations. label Apr 28, 2026
@hardikdr hardikdr added this to Roadmap Apr 28, 2026
@felix-kaestner felix-kaestner force-pushed the feat/unsupported-fields branch 4 times, most recently from 9d34c07 to f7c97e3 Compare May 11, 2026 08:26
@felix-kaestner felix-kaestner force-pushed the feat/unsupported-fields branch from f7c97e3 to 74b89da Compare May 11, 2026 09:08
Comment thread internal/apistatus/apistatus.go Outdated
// if err != nil {
// return apistatus.WrapTerminalError(err)
// }
func WrapTerminalError(err error) error {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we wrap grpc errors to TerminalError already? Does it make sense to combine this in one function?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we do, but it doesn't make sense to combine this. grpc errors would be returned when we actually make a gNMI call. These apistatus errors are used for input validation, even before making any gNMI call. Hence they are also checked at different code paths.

@@ -29,7 +29,10 @@ const (
CodeUnsupportedField

// CodeFailedPrecondition signals that a precondition on the device or
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the idea just popped into my head, unsure if that make sense, but would it be helpful to make retry configurable? For most of the cases probably not, just wanted tho share my thoughts here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice idea. I think I would leave this up to future follow-ups in case we really need it. Currently, as this Error is used for the NX-OS implementation, we agreed that we are fine to keep the backup/retry behavior (making it non-terminal) so that it has the chance to eventually resolve itself.

Introduces internal/apistatus, a leaf package that gives provider
implementations a structured way to return non-retryable errors to
controllers without coupling directly to reconcile.TerminalError.

The package defines a Code enum (CodeInvalidArgument, CodeUnsupportedField,
CodeFailedPrecondition) with String() and Valid() methods, a StatusError
struct carrying a Code, an optional Message, and optional FieldViolations,
and constructor functions for each code. StatusError implements Is() so
that errors.Is can be used for type detection across error chains.
WrapTerminalError wraps any error containing a *StatusError as a
reconcile.TerminalError, leaving all other errors unchanged.

conditions.FromError is extended to detect *StatusError before the
existing gRPC status path, mapping the code to the condition Reason
and the formatted message to the condition Message field.

The stub internal/controllerutil package, which this supersedes, has
been removed as no code imported it.

Signed-off-by: Felix Kästner <felix.kaestner@sap.com>
Each controller's Reconcile method now passes provider errors through
apistatus.WrapTerminalError before returning them to controller-runtime.

This ensures that any *StatusError returned by a provider implementation
— signalling an unsupported field, an invalid argument, or an unmet
precondition — suppresses retry backoff and is treated as a terminal
error by the reconciler. Transient errors and Kubernetes-level
precondition failures are returned unchanged, preserving existing
retry behaviour for those cases.

Signed-off-by: Felix Kästner <felix.kaestner@sap.com>
Replace plain fmt.Errorf/errors.New calls in the NX-OS provider and
name.go with typed apistatus errors so non-retryable failures surface
as structured Kubernetes status conditions with a stable Reason field.

Signed-off-by: Felix Kästner <felix.kaestner@sap.com>
WrapTerminalError now only promotes CodeInvalidArgument and
CodeUnsupportedField to reconcile.TerminalError. CodeFailedPrecondition
errors pass through unchanged so controller-runtime exponential backoff
can requeue them — the precondition (e.g. a BGP process) may become
true on a future attempt.

Update docs and tests to reflect the new behaviour.

Signed-off-by: Felix Kästner <felix.kaestner@sap.com>
@felix-kaestner felix-kaestner force-pushed the feat/unsupported-fields branch from 6472af5 to f87cad7 Compare May 18, 2026 09:07
@github-actions
Copy link
Copy Markdown

Merging this branch changes the coverage (1 decrease, 2 increase)

Impacted Packages Coverage Δ 🤖
github.com/ironcore-dev/network-operator/internal/apistatus 96.30% (+96.30%) 🌟
github.com/ironcore-dev/network-operator/internal/conditions 75.38% (-4.94%) 👎
github.com/ironcore-dev/network-operator/internal/controller/cisco/nx 65.77% (+0.72%) 👍
github.com/ironcore-dev/network-operator/internal/controller/core 62.76% (ø)
github.com/ironcore-dev/network-operator/internal/provider/cisco/nxos 9.94% (ø)

Coverage by file

Changed files (no unit tests)

Changed File Coverage Δ Total Covered Missed 🤖
github.com/ironcore-dev/network-operator/internal/apistatus/apistatus.go 96.30% (+96.30%) 27 (+27) 26 (+26) 1 (+1) 🌟
github.com/ironcore-dev/network-operator/internal/conditions/conditions.go 75.38% (-4.94%) 65 (+4) 49 16 (+4) 👎
github.com/ironcore-dev/network-operator/internal/controller/cisco/nx/bordergateway_controller.go 52.97% (ø) 236 125 111
github.com/ironcore-dev/network-operator/internal/controller/cisco/nx/system_controller.go 65.52% (ø) 116 76 40
github.com/ironcore-dev/network-operator/internal/controller/cisco/nx/vpcdomain_controller.go 80.58% (+1.94%) 206 166 (+4) 40 (-4) 👍
github.com/ironcore-dev/network-operator/internal/controller/core/acl_controller.go 58.16% (ø) 141 82 59
github.com/ironcore-dev/network-operator/internal/controller/core/banner_controller.go 57.99% (ø) 169 98 71
github.com/ironcore-dev/network-operator/internal/controller/core/bgp_controller.go 68.53% (ø) 232 159 73
github.com/ironcore-dev/network-operator/internal/controller/core/bgp_peer_controller.go 57.01% (ø) 314 179 135
github.com/ironcore-dev/network-operator/internal/controller/core/certificate_controller.go 58.71% (ø) 155 91 64
github.com/ironcore-dev/network-operator/internal/controller/core/dhcprelay_controller.go 65.89% (ø) 258 170 88
github.com/ironcore-dev/network-operator/internal/controller/core/dns_controller.go 57.97% (ø) 138 80 58
github.com/ironcore-dev/network-operator/internal/controller/core/evpninstance_controller.go 66.35% (ø) 208 138 70
github.com/ironcore-dev/network-operator/internal/controller/core/interface_controller.go 65.52% (-0.36%) 551 361 (-2) 190 (+2) 👎
github.com/ironcore-dev/network-operator/internal/controller/core/isis_controller.go 60.12% (ø) 173 104 69
github.com/ironcore-dev/network-operator/internal/controller/core/lldp_controller.go 69.59% (ø) 217 151 66
github.com/ironcore-dev/network-operator/internal/controller/core/managementaccess_controller.go 57.97% (ø) 138 80 58
github.com/ironcore-dev/network-operator/internal/controller/core/ntp_controller.go 57.97% (ø) 138 80 58
github.com/ironcore-dev/network-operator/internal/controller/core/nve_controller.go 67.59% (-0.46%) 216 146 (-1) 70 (+1) 👎
github.com/ironcore-dev/network-operator/internal/controller/core/ospf_controller.go 59.80% (ø) 204 122 82
github.com/ironcore-dev/network-operator/internal/controller/core/pim_controller.go 60.12% (ø) 173 104 69
github.com/ironcore-dev/network-operator/internal/controller/core/prefixset_controller.go 60.99% (-0.71%) 141 86 (-1) 55 (+1) 👎
github.com/ironcore-dev/network-operator/internal/controller/core/routingpolicy_controller.go 67.54% (+1.05%) 191 129 (+2) 62 (-2) 👍
github.com/ironcore-dev/network-operator/internal/controller/core/snmp_controller.go 57.97% (ø) 138 80 58
github.com/ironcore-dev/network-operator/internal/controller/core/syslog_controller.go 58.16% (ø) 141 82 59
github.com/ironcore-dev/network-operator/internal/controller/core/user_controller.go 56.80% (ø) 169 96 73
github.com/ironcore-dev/network-operator/internal/controller/core/vlan_controller.go 62.00% (ø) 150 93 57
github.com/ironcore-dev/network-operator/internal/controller/core/vrf_controller.go 62.32% (ø) 138 86 52
github.com/ironcore-dev/network-operator/internal/provider/cisco/nxos/name.go 61.54% (ø) 26 16 10
github.com/ironcore-dev/network-operator/internal/provider/cisco/nxos/provider.go 0.06% (ø) 1779 1 1778

Please note that the "Total", "Covered", and "Missed" counts above refer to code statements instead of lines of code. The value in brackets refers to the test coverage of that file in the old version of the code.

Changed unit test files

  • github.com/ironcore-dev/network-operator/internal/apistatus/apistatus_test.go

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/switch-automation Automation processes for network switch management and operations. size/L

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

3 participants