Skip to content

Add Configured condition to all controllers - #426

Open
adamtrizuljak-sap wants to merge 7 commits into
mainfrom
feat/configured-condition
Open

Add Configured condition to all controllers#426
adamtrizuljak-sap wants to merge 7 commits into
mainfrom
feat/configured-condition

Conversation

@adamtrizuljak-sap

@adamtrizuljak-sap adamtrizuljak-sap commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Previously, some controllers did not set the Configured condition after performing the configuration. They only set the Ready condition, which was ambiguous. This PR explicitly sets the Configured condition based on the provider success and ensures that the Ready condition is set correctly at the end of the reconcile loop.

  • Initialize the ConfiguredCondition to ConditionFalse with ReconcilePendingReason to ensure consistency if the controller exits early due to an error
  • Add deferred call of conditions.RecomputeReady() to ensure the Ready condition is evaluated and set at the end of the reconcile loop
  • cond := conditions.FromError(err) already returns the Configured condition, so we just remove the next line that was overriding it with the Ready condition
  • Update the associated tests to check that the Configured condition has been set
  • Add Kubebuilder printcolumn for the Configured condition

Todo

  • In BGP controller, EVPNInstance (and other) controllers check if it's correct to just replace the ReadyCondition to ConfiguredCondition - I suspect both conditions should be set
  • Fix tests
  • Squash commits before merging

Test reliability

While working on this PR I discovered issues with tests that cause typically 1-2 failures out of 10 runs of the test suite. See my comment below. I've spent lots of time trying to chase complex race conditions and managed to fix some of the failure modes, including:

  • Adding waiting steps for resources to be truly created / deleted - helps avoid deadlocks when referenced resources were never getting deleted
  • Adding missing RequeueInterval to EthernetSegmentReconciler because it was not getting re-queued for reconciliation

I've separated the test fixes into a separate PR #472 which has already been merged.

However the effort has reached diminishing returns while significantly delaying the completion of this PR. Therefore we've decided to merge this PR and continue to work on improving the tests in follow-up work.

Comment thread internal/controller/core/bgp_controller.go

@felix-kaestner felix-kaestner left a comment

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.

As we are adding a new condition to these resources, should we also add a printcolumn like so?

diff --git a/api/core/v1alpha1/acl_types.go b/api/core/v1alpha1/acl_types.go
index 6501f362..a7a01b70 100644
--- a/api/core/v1alpha1/acl_types.go
+++ b/api/core/v1alpha1/acl_types.go
@@ -122,6 +122,7 @@ type AccessControlListStatus struct {
 // +kubebuilder:printcolumn:name="Device",type=string,JSONPath=`.spec.deviceRef.name`
 // +kubebuilder:printcolumn:name="Entries",type=string,JSONPath=`.status.entriesSummary`,priority=1
 // +kubebuilder:printcolumn:name="Ready",type=string,JSONPath=`.status.conditions[?(@.type=="Ready")].status`
+// +kubebuilder:printcolumn:name="Configured",type=string,JSONPath=`.status.conditions[?(@.type=="Configured")].status`,priority=1
 // +kubebuilder:printcolumn:name="Paused",type=string,JSONPath=`.status.conditions[?(@.type=="Paused")].status`,priority=1
 // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"

@adamtrizuljak-sap
adamtrizuljak-sap force-pushed the feat/configured-condition branch 2 times, most recently from 032b437 to 8d76d7d Compare July 6, 2026 12:47
@github-actions github-actions Bot added size/M and removed size/L labels Jul 6, 2026
@adamtrizuljak-sap
adamtrizuljak-sap marked this pull request as ready for review July 6, 2026 13:35
@adamtrizuljak-sap
adamtrizuljak-sap requested a review from a team July 6, 2026 13:35
Comment thread internal/controller/core/dhcprelay_controller.go
@nikatza

nikatza commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

@adamtrizuljak-sap We merged the aaa resource in the mean time, which unfortunately has the same issue that you fix in this PR. Could you please update your PR and include a fix for it?

Likewise, in this PR we only tackle core resources. There are some other resources like bordergateway and system in cisco/nx that could use this fix too.

Thanks a lot for your contribution! 🔥

@adamtrizuljak-sap

Copy link
Copy Markdown
Contributor Author

@adamtrizuljak-sap We merged the aaa resource in the mean time, which unfortunately has the same issue that you fix in this PR. Could you please update your PR and include a fix for it?

Likewise, in this PR we only tackle core resources. There are some other resources like bordergateway and system in cisco/nx that could use this fix too.

Thanks a lot for your contribution! 🔥

Done. Are there any other resources that need this change?

@felix-kaestner felix-kaestner left a comment

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.

We currently have some places in the codebase that use the check for IsReady on resources, that previously only had the Ready condition, e.g.

if !conditions.IsReady(vrf) {
// VRF uses ReadyCondition as its top-level configured state (no separate ConfiguredCondition).

This is combined with a watch trigger to do reconcilation once this condition changes, e.g.

return conditions.IsReady(oldVRF) != conditions.IsReady(newVRF)

Now that resources like the VRF get a configured condition, I think those checks should also be adjusted to use the IsConfigured check instead of the IsReady check,

// IsConfigured looks at the [v1alpha1.ConfiguredCondition] condition type and returns true
// if that condition is set to true and the observed generation matches the object's generation.
func IsConfigured(target Getter) bool {
condition := Get(target, v1alpha1.ConfiguredCondition)
if condition == nil {
return false
}
if m, ok := target.(metav1.Object); ok && condition.ObservedGeneration != m.GetGeneration() {
return false
}
return condition.Status == metav1.ConditionTrue
}

So I think one additional task would be to go through the codebase and find usages of IsReady which were used for resources that only had the Ready condition previously and change those to now check for IsConfigured instead.

@adamtrizuljak-sap
adamtrizuljak-sap force-pushed the feat/configured-condition branch from 1b654c2 to 4ead8e3 Compare July 9, 2026 11:29
@adamtrizuljak-sap

adamtrizuljak-sap commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

We currently have some places in the codebase that use the check for IsReady on resources, that previously only had the Ready condition, e.g.

if !conditions.IsReady(vrf) {
// VRF uses ReadyCondition as its top-level configured state (no separate ConfiguredCondition).

...

@felix-kaestner I think that this should not be an issue or a change in behavior? At the end of reconciliation, RecomputeReady() loops over all conditions of the resource and if any of the conditions is False, it sets the Ready condition to False

https://github.com/ironcore-dev/network-operator/blob/main/internal/conditions/conditions.go#L119

Therefore if the Configured condition is False, the Ready condition will also be False and the following check should still have the same behavior. Plus it will trigger the reconciliation if any other condition becomes False, not just the Configured condition

This is combined with a watch trigger to do reconcilation once this condition changes, e.g.

return conditions.IsReady(oldVRF) != conditions.IsReady(newVRF)

Do you think that we should also explicitly evaluate the Configured condition?

@felix-kaestner

Copy link
Copy Markdown
Contributor

Do you think that we should also explicitly evaluate the Configured condition?

Yes, I think that would be more semantically correct here, as the intention is "I can't configure X before configuring Y". The reason this currently uses the "Ready" condition was simply that these resources didn't have a "Configured" condition, as is also commented in those places. Now that we introduce the "Configured" condition I think we should change that.

@adamtrizuljak-sap
adamtrizuljak-sap force-pushed the feat/configured-condition branch from 4ead8e3 to d5f5438 Compare July 13, 2026 14:53
@adamtrizuljak-sap

Copy link
Copy Markdown
Contributor Author

Do you think that we should also explicitly evaluate the Configured condition?

Yes, I think that would be more semantically correct here, as the intention is "I can't configure X before configuring Y". The reason this currently uses the "Ready" condition was simply that these resources didn't have a "Configured" condition, as is also commented in those places. Now that we introduce the "Configured" condition I think we should change that.

Got it. @nikatza helped me understand the problem - you were talking about only about the controllers that depend on other referenced resources.

For example the BGPPeer controller watches referenced BGP and VRF resources and triggers a self-reconciliation when their conditions change. Since these resources now expose a proper Configured condition, we update the checking logic to use it instead of the Ready condition (which now has too broad meaning).

@adamtrizuljak-sap
adamtrizuljak-sap force-pushed the feat/configured-condition branch from d5f5438 to 691ea9a Compare July 14, 2026 09:12
@swagner-de
swagner-de marked this pull request as draft July 23, 2026 12:47
@adamtrizuljak-sap
adamtrizuljak-sap force-pushed the feat/configured-condition branch from 691ea9a to 2434e13 Compare July 24, 2026 09:54
@adamtrizuljak-sap

Copy link
Copy Markdown
Contributor Author

Update after long time, as I've been (and still am) trying to fix a regression in test reliability...

The Configured condition is being updated at the end of reconcile(). If an error happens earlier in reconcile(), this part is never reached and the condition stays outdated. This caused tests to fail randomly, where one error in one resource's controller would cause a cascade of subsequent test case failures due to conditions not being updated. Especially tests and other logic that uses conditions.IsConfigured(), which checks if condition.ObservedGeneration matches the object's generation. Resources that reference other resources were more likely to fail, because the probabilities of failure in the referenced resources add up. For example BGPPeer which references VRF, BGP and RoutingPolicy. Overall failure rate was approx. 3-4 fails out of 10 runs of the full test suite.

My idea to fix this is to explicitly set the Configured condition to False or Unknown early in the reconcile() function. Therefore if it exits early due to an error, the condition will be left in a consistent state and its ObservedGeneration will be up-to-date. Only if all actions in reconcile() succeed, we get to the end and actually set the condition based on the result of Provider.EnsureXXX()

conditions.Set(s.BGPPeer, metav1.Condition{
	Type:   v1alpha1.ConfiguredCondition,
	Status: metav1.ConditionFalse,
})

// Controller code which may return early due to error...

err = s.Provider.EnsureBGPPeer(ctx, &provider.EnsureBGPPeerRequest{...})
cond := conditions.FromError(err)
conditions.Set(s.BGPPeer, cond)

This method has improved the test suite reliability to less than 1 failure out of 10 runs, but it's still not quite there. I will likely have to add this fix to all controllers that have the Configured condition.

Previously, some controllers did not set the Configured condition after
performing the configuration. They only set the Ready condition, which
was ambiguous. This PR explicitly sets the Configured condition based on
the provider success and ensures that the Ready condition is set
correctly at the end of the reconcile loop.

- Initialize the `ConfiguredCondition`
- Add deferred call of `conditions.RecomputeReady()` to ensure the Ready condition is evaluated and set at the end of the reconcile loop
- `cond := conditions.FromError(err)` already returns the Configured condition, so we just remove the next line that was overriding it with the Ready condition
- Check `Configured` condition on referenced resources
- Update the associated tests to check that the Configured condition has been set
- Add Kubebuilder printcolumn for the Configured condition

Some controllers depend on referenced resources. E.g. BGPPeer controller watches referenced BGP and VRF resources and triggers a self-reconciliation if their status changes. Since these resources now expose a proper Configured condition, we update the checking logic to use it instead of the Ready condition (which is now too broad)

Signed-off-by: Adam Trizuljak <adam.trizuljak@sap.com>
Signed-off-by: Adam Trizuljak <adam.trizuljak@sap.com>
Signed-off-by: Adam Trizuljak <adam.trizuljak@sap.com>
Signed-off-by: Adam Trizuljak <adam.trizuljak@sap.com>
Initialize it to False with ReconcilePendingReason. If the controller exits early due to error before it reaches the point where the condition is updated to the actual value, the condition will be left in a consistent state

Signed-off-by: Adam Trizuljak <adam.trizuljak@sap.com>
Signed-off-by: Adam Trizuljak <adam.trizuljak@sap.com>
Signed-off-by: Adam Trizuljak <adam.trizuljak@sap.com>
@adamtrizuljak-sap
adamtrizuljak-sap force-pushed the feat/configured-condition branch from e6399fd to a151482 Compare August 5, 2026 13:13
@adamtrizuljak-sap
adamtrizuljak-sap marked this pull request as ready for review August 5, 2026 13:13
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown

Merging this branch will increase overall coverage

Impacted Packages Coverage Δ 🤖
github.com/ironcore-dev/network-operator/api/cisco/nx/v1alpha1 0.00% (ø)
github.com/ironcore-dev/network-operator/api/core/v1alpha1 1.72% (ø)
github.com/ironcore-dev/network-operator/internal/controller/cisco/nx 66.07% (+1.38%) 👍
github.com/ironcore-dev/network-operator/internal/controller/core 62.46% (+0.78%) 👍

Coverage by file

Changed files (no unit tests)

Changed File Coverage Δ Total Covered Missed 🤖
github.com/ironcore-dev/network-operator/api/cisco/nx/v1alpha1/bordergateway_types.go 0.00% (ø) 5 0 5
github.com/ironcore-dev/network-operator/api/cisco/nx/v1alpha1/system_types.go 0.00% (ø) 5 0 5
github.com/ironcore-dev/network-operator/api/core/v1alpha1/aaa_types.go 12.50% (ø) 8 1 7
github.com/ironcore-dev/network-operator/api/core/v1alpha1/acl_types.go 12.50% (ø) 8 1 7
github.com/ironcore-dev/network-operator/api/core/v1alpha1/banner_types.go 12.50% (ø) 8 1 7
github.com/ironcore-dev/network-operator/api/core/v1alpha1/bgp_types.go 12.50% (ø) 8 1 7
github.com/ironcore-dev/network-operator/api/core/v1alpha1/certificate_types.go 12.50% (ø) 8 1 7
github.com/ironcore-dev/network-operator/api/core/v1alpha1/dhcprelay_types.go 12.50% (ø) 8 1 7
github.com/ironcore-dev/network-operator/api/core/v1alpha1/dns_types.go 12.50% (ø) 8 1 7
github.com/ironcore-dev/network-operator/api/core/v1alpha1/evpninstance_types.go 12.50% (ø) 8 1 7
github.com/ironcore-dev/network-operator/api/core/v1alpha1/isis_types.go 12.50% (ø) 8 1 7
github.com/ironcore-dev/network-operator/api/core/v1alpha1/managementaccess_types.go 12.50% (ø) 8 1 7
github.com/ironcore-dev/network-operator/api/core/v1alpha1/ntp_types.go 12.50% (ø) 8 1 7
github.com/ironcore-dev/network-operator/api/core/v1alpha1/pim_types.go 12.50% (ø) 8 1 7
github.com/ironcore-dev/network-operator/api/core/v1alpha1/prefixset_types.go 10.00% (ø) 10 1 9
github.com/ironcore-dev/network-operator/api/core/v1alpha1/routingpolicy_types.go 12.50% (ø) 8 1 7
github.com/ironcore-dev/network-operator/api/core/v1alpha1/snmp_types.go 12.50% (ø) 8 1 7
github.com/ironcore-dev/network-operator/api/core/v1alpha1/syslog_types.go 12.50% (ø) 8 1 7
github.com/ironcore-dev/network-operator/api/core/v1alpha1/user_types.go 12.50% (ø) 8 1 7
github.com/ironcore-dev/network-operator/api/core/v1alpha1/vrf_types.go 12.50% (ø) 8 1 7
github.com/ironcore-dev/network-operator/internal/controller/cisco/nx/bordergateway_controller.go 54.20% (+1.24%) 238 (+2) 129 (+4) 109 (-2) 👍
github.com/ironcore-dev/network-operator/internal/controller/cisco/nx/system_controller.go 67.80% (+2.28%) 118 (+2) 80 (+4) 38 (-2) 👍
github.com/ironcore-dev/network-operator/internal/controller/cisco/nx/vpcdomain_controller.go 78.74% (+1.07%) 207 (+1) 163 (+3) 44 (-2) 👍
github.com/ironcore-dev/network-operator/internal/controller/core/aaa_controller.go 0.00% (ø) 141 (+2) 0 141 (+2)
github.com/ironcore-dev/network-operator/internal/controller/core/acl_controller.go 60.14% (+1.98%) 143 (+2) 86 (+4) 57 (-2) 👍
github.com/ironcore-dev/network-operator/internal/controller/core/banner_controller.go 59.65% (+1.66%) 171 (+2) 102 (+4) 69 (-2) 👍
github.com/ironcore-dev/network-operator/internal/controller/core/bgp_controller.go 70.71% (+1.09%) 239 (+2) 169 (+4) 70 (-2) 👍
github.com/ironcore-dev/network-operator/internal/controller/core/bgp_peer_controller.go 58.20% (+0.13%) 323 (+1) 188 (+1) 135 👍
github.com/ironcore-dev/network-operator/internal/controller/core/certificate_controller.go 59.87% (+0.52%) 157 (+2) 94 (+2) 63 👍
github.com/ironcore-dev/network-operator/internal/controller/core/dhcprelay_controller.go 66.15% (+0.26%) 260 (+2) 172 (+2) 88 👍
github.com/ironcore-dev/network-operator/internal/controller/core/dns_controller.go 62.14% (+0.55%) 140 (+2) 87 (+2) 53 👍
github.com/ironcore-dev/network-operator/internal/controller/core/ethernetsegment_controller.go 66.51% (+0.16%) 209 (+1) 139 (+1) 70 👍
github.com/ironcore-dev/network-operator/internal/controller/core/evpninstance_controller.go 63.42% (+1.07%) 257 (+2) 163 (+4) 94 (-2) 👍
github.com/ironcore-dev/network-operator/internal/controller/core/isis_controller.go 61.27% (+1.16%) 173 106 (+2) 67 (-2) 👍
github.com/ironcore-dev/network-operator/internal/controller/core/lldp_controller.go 69.72% (+0.14%) 218 (+1) 152 (+1) 66 👍
github.com/ironcore-dev/network-operator/internal/controller/core/managementaccess_controller.go 60.00% (+2.03%) 140 (+2) 84 (+4) 56 (-2) 👍
github.com/ironcore-dev/network-operator/internal/controller/core/ntp_controller.go 60.00% (+2.03%) 140 (+2) 84 (+4) 56 (-2) 👍
github.com/ironcore-dev/network-operator/internal/controller/core/nve_controller.go 68.66% (+1.07%) 217 (+1) 149 (+3) 68 (-2) 👍
github.com/ironcore-dev/network-operator/internal/controller/core/ospf_controller.go 60.00% (+0.20%) 205 (+1) 123 (+1) 82 👍
github.com/ironcore-dev/network-operator/internal/controller/core/pim_controller.go 63.58% (+3.93%) 173 (+2) 110 (+8) 63 (-6) 👍
github.com/ironcore-dev/network-operator/internal/controller/core/prefixset_controller.go 62.94% (+1.23%) 143 (+2) 90 (+3) 53 (-1) 👍
github.com/ironcore-dev/network-operator/internal/controller/core/routingpolicy_controller.go 68.39% (+0.33%) 193 (+2) 132 (+2) 61 👍
github.com/ironcore-dev/network-operator/internal/controller/core/snmp_controller.go 60.00% (+2.03%) 140 (+2) 84 (+4) 56 (-2) 👍
github.com/ironcore-dev/network-operator/internal/controller/core/syslog_controller.go 60.14% (+1.98%) 143 (+2) 86 (+4) 57 (-2) 👍
github.com/ironcore-dev/network-operator/internal/controller/core/user_controller.go 58.48% (+1.67%) 171 (+2) 100 (+4) 71 (-2) 👍
github.com/ironcore-dev/network-operator/internal/controller/core/vlan_controller.go 62.25% (+0.25%) 151 (+1) 94 (+1) 57 👍
github.com/ironcore-dev/network-operator/internal/controller/core/vrf_controller.go 62.86% (+0.54%) 140 (+2) 88 (+2) 52 👍

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/controller/cisco/nx/bordergateway_controller_test.go
  • github.com/ironcore-dev/network-operator/internal/controller/cisco/nx/system_controller_test.go
  • github.com/ironcore-dev/network-operator/internal/controller/core/acl_controller_test.go
  • github.com/ironcore-dev/network-operator/internal/controller/core/banner_controller_test.go
  • github.com/ironcore-dev/network-operator/internal/controller/core/bgp_controller_test.go
  • github.com/ironcore-dev/network-operator/internal/controller/core/bgp_peer_controller_test.go
  • github.com/ironcore-dev/network-operator/internal/controller/core/certificate_controller_test.go
  • github.com/ironcore-dev/network-operator/internal/controller/core/dhcprelay_controller_test.go
  • github.com/ironcore-dev/network-operator/internal/controller/core/dns_controller_test.go
  • github.com/ironcore-dev/network-operator/internal/controller/core/evpninstance_controller_test.go
  • github.com/ironcore-dev/network-operator/internal/controller/core/isis_controller_test.go
  • github.com/ironcore-dev/network-operator/internal/controller/core/managementaccess_controller_test.go
  • github.com/ironcore-dev/network-operator/internal/controller/core/ntp_controller_test.go
  • github.com/ironcore-dev/network-operator/internal/controller/core/pim_controller_test.go
  • github.com/ironcore-dev/network-operator/internal/controller/core/prefixset_controller_test.go
  • github.com/ironcore-dev/network-operator/internal/controller/core/routingpolicy_controller_test.go
  • github.com/ironcore-dev/network-operator/internal/controller/core/snmp_controller_test.go
  • github.com/ironcore-dev/network-operator/internal/controller/core/suite_test.go
  • github.com/ironcore-dev/network-operator/internal/controller/core/syslog_controller_test.go
  • github.com/ironcore-dev/network-operator/internal/controller/core/user_controller_test.go
  • github.com/ironcore-dev/network-operator/internal/controller/core/vlan_controller_test.go
  • github.com/ironcore-dev/network-operator/internal/controller/core/vrf_controller_test.go

Comment on lines +334 to +339
conditions.Set(s.BorderGateway, metav1.Condition{
Type: v1alpha1.ConfiguredCondition,
Status: metav1.ConditionFalse,
Reason: v1alpha1.ReconcilePendingReason,
Message: "Reconciliation is in progress",
})

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.

General Question: Did we agree that we want to reset the status condition like this at the start of the reconcilation?

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.

I think we discussed this on our sync last week and we agreed on this, but correct me if I'm wrong. We can discuss it again today if needed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Have we validated this outside of the tests? I would expect the resetting on the beginning of the reconciliation to cause a reconciliation loop since the objects status is changed during each iteration. As soon as the status is flipped the condition will get a new LastTransitionedAt timestamp, so even for an otherwise noop each reconciliation will end up requeueing the object.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants