From ab68e99b875fece22f447ad5192f09ee0ca2e936 Mon Sep 17 00:00:00 2001 From: Adam Trizuljak Date: Tue, 23 Jun 2026 11:12:44 +0200 Subject: [PATCH 01/12] Add Configured condition to all controllers 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 --- api/cisco/nx/v1alpha1/bordergateway_types.go | 1 + api/cisco/nx/v1alpha1/system_types.go | 1 + api/core/v1alpha1/aaa_types.go | 1 + api/core/v1alpha1/acl_types.go | 1 + api/core/v1alpha1/banner_types.go | 1 + api/core/v1alpha1/bgp_types.go | 1 + api/core/v1alpha1/certificate_types.go | 1 + api/core/v1alpha1/dhcprelay_types.go | 1 + api/core/v1alpha1/dns_types.go | 1 + api/core/v1alpha1/evpninstance_types.go | 1 + api/core/v1alpha1/isis_types.go | 1 + api/core/v1alpha1/managementaccess_types.go | 1 + api/core/v1alpha1/ntp_types.go | 1 + api/core/v1alpha1/pim_types.go | 1 + api/core/v1alpha1/prefixset_types.go | 1 + api/core/v1alpha1/routingpolicy_types.go | 1 + api/core/v1alpha1/snmp_types.go | 1 + api/core/v1alpha1/syslog_types.go | 1 + api/core/v1alpha1/user_types.go | 1 + api/core/v1alpha1/vrf_types.go | 1 + .../aaa.networking.metal.ironcore.dev.yaml | 4 ++ ...ollists.networking.metal.ironcore.dev.yaml | 4 ++ ...banners.networking.metal.ironcore.dev.yaml | 4 ++ .../bgp.networking.metal.ironcore.dev.yaml | 4 ++ ...x.cisco.networking.metal.ironcore.dev.yaml | 4 ++ ...ficates.networking.metal.ironcore.dev.yaml | 4 ++ ...prelays.networking.metal.ironcore.dev.yaml | 4 ++ .../dns.networking.metal.ironcore.dev.yaml | 4 ++ ...stances.networking.metal.ironcore.dev.yaml | 4 ++ .../isis.networking.metal.ironcore.dev.yaml | 4 ++ ...ccesses.networking.metal.ironcore.dev.yaml | 4 ++ .../ntp.networking.metal.ironcore.dev.yaml | 4 ++ .../pim.networking.metal.ironcore.dev.yaml | 4 ++ ...fixsets.networking.metal.ironcore.dev.yaml | 4 ++ ...olicies.networking.metal.ironcore.dev.yaml | 4 ++ .../snmp.networking.metal.ironcore.dev.yaml | 4 ++ ...syslogs.networking.metal.ironcore.dev.yaml | 4 ++ ...x.cisco.networking.metal.ironcore.dev.yaml | 4 ++ .../users.networking.metal.ironcore.dev.yaml | 4 ++ .../vrfs.networking.metal.ironcore.dev.yaml | 4 ++ .../networking.metal.ironcore.dev_aaa.yaml | 4 ++ ...metal.ironcore.dev_accesscontrollists.yaml | 4 ++ ...networking.metal.ironcore.dev_banners.yaml | 4 ++ .../networking.metal.ironcore.dev_bgp.yaml | 4 ++ ...rking.metal.ironcore.dev_certificates.yaml | 4 ++ ...working.metal.ironcore.dev_dhcprelays.yaml | 4 ++ .../networking.metal.ironcore.dev_dns.yaml | 4 ++ ...king.metal.ironcore.dev_evpninstances.yaml | 4 ++ .../networking.metal.ironcore.dev_isis.yaml | 4 ++ ...metal.ironcore.dev_managementaccesses.yaml | 4 ++ .../networking.metal.ironcore.dev_ntp.yaml | 4 ++ .../networking.metal.ironcore.dev_pim.yaml | 4 ++ ...working.metal.ironcore.dev_prefixsets.yaml | 4 ++ ...ng.metal.ironcore.dev_routingpolicies.yaml | 4 ++ .../networking.metal.ironcore.dev_snmp.yaml | 4 ++ ...networking.metal.ironcore.dev_syslogs.yaml | 4 ++ .../networking.metal.ironcore.dev_users.yaml | 4 ++ .../networking.metal.ironcore.dev_vrfs.yaml | 4 ++ ...ing.metal.ironcore.dev_bordergateways.yaml | 4 ++ ...networking.metal.ironcore.dev_systems.yaml | 4 ++ .../cisco/nx/bordergateway_controller.go | 24 ++++++----- .../cisco/nx/bordergateway_controller_test.go | 8 ++-- .../controller/cisco/nx/system_controller.go | 8 ++-- .../cisco/nx/system_controller_test.go | 8 ++-- internal/controller/core/aaa_controller.go | 8 ++-- internal/controller/core/acl_controller.go | 8 ++-- .../controller/core/acl_controller_test.go | 8 ++-- internal/controller/core/banner_controller.go | 8 ++-- .../controller/core/banner_controller_test.go | 16 +++++--- internal/controller/core/bgp_controller.go | 22 +++++----- .../controller/core/bgp_controller_test.go | 16 ++++---- .../controller/core/bgp_peer_controller.go | 8 ++-- .../controller/core/certificate_controller.go | 8 ++-- .../core/certificate_controller_test.go | 8 ++-- .../controller/core/dhcprelay_controller.go | 10 +++-- .../core/dhcprelay_controller_test.go | 10 +++-- internal/controller/core/dns_controller.go | 8 ++-- .../controller/core/dns_controller_test.go | 8 ++-- .../core/evpninstance_controller.go | 14 ++++--- .../core/evpninstance_controller_test.go | 24 ++++++----- internal/controller/core/isis_controller.go | 12 +++--- .../controller/core/isis_controller_test.go | 8 ++-- .../core/managementaccess_controller.go | 8 ++-- .../core/managementaccess_controller_test.go | 8 ++-- internal/controller/core/ntp_controller.go | 8 ++-- .../controller/core/ntp_controller_test.go | 8 ++-- internal/controller/core/pim_controller.go | 12 +++--- .../controller/core/pim_controller_test.go | 14 ++++--- .../controller/core/prefixset_controller.go | 8 ++-- .../core/prefixset_controller_test.go | 8 ++-- .../core/routingpolicy_controller.go | 12 +++--- .../core/routingpolicy_controller_test.go | 40 ++++++++++++------- internal/controller/core/snmp_controller.go | 8 ++-- .../controller/core/snmp_controller_test.go | 8 ++-- internal/controller/core/syslog_controller.go | 8 ++-- .../controller/core/syslog_controller_test.go | 8 ++-- internal/controller/core/user_controller.go | 8 ++-- .../controller/core/user_controller_test.go | 8 ++-- internal/controller/core/vrf_controller.go | 8 ++-- .../controller/core/vrf_controller_test.go | 8 ++-- 100 files changed, 446 insertions(+), 176 deletions(-) diff --git a/api/cisco/nx/v1alpha1/bordergateway_types.go b/api/cisco/nx/v1alpha1/bordergateway_types.go index 9f1b02202..f1aa2756a 100644 --- a/api/cisco/nx/v1alpha1/bordergateway_types.go +++ b/api/cisco/nx/v1alpha1/bordergateway_types.go @@ -167,6 +167,7 @@ type BorderGatewayStatus struct { // +kubebuilder:printcolumn:name="Admin State",type=string,JSONPath=`.spec.adminState` // +kubebuilder:printcolumn:name="Source Interface",type=string,JSONPath=`.spec.sourceInterfaceRef.name` // +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" diff --git a/api/cisco/nx/v1alpha1/system_types.go b/api/cisco/nx/v1alpha1/system_types.go index 12e671e37..b33704359 100644 --- a/api/cisco/nx/v1alpha1/system_types.go +++ b/api/cisco/nx/v1alpha1/system_types.go @@ -61,6 +61,7 @@ type SystemStatus struct { // +kubebuilder:resource:shortName=nxsystem // +kubebuilder:printcolumn:name="Device",type=string,JSONPath=`.spec.deviceRef.name` // +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" diff --git a/api/core/v1alpha1/aaa_types.go b/api/core/v1alpha1/aaa_types.go index ab6b61e43..0564627f6 100644 --- a/api/core/v1alpha1/aaa_types.go +++ b/api/core/v1alpha1/aaa_types.go @@ -230,6 +230,7 @@ type AAAStatus struct { // +kubebuilder:resource:singular=aaa // +kubebuilder:printcolumn:name="Device",type=string,JSONPath=`.spec.deviceRef.name` // +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="Age",type="date",JSONPath=".metadata.creationTimestamp" // AAA is the Schema for the aaa API diff --git a/api/core/v1alpha1/acl_types.go b/api/core/v1alpha1/acl_types.go index 6501f3625..a7a01b708 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" diff --git a/api/core/v1alpha1/banner_types.go b/api/core/v1alpha1/banner_types.go index e9de15b87..25a8cdbaa 100644 --- a/api/core/v1alpha1/banner_types.go +++ b/api/core/v1alpha1/banner_types.go @@ -67,6 +67,7 @@ type BannerStatus struct { // +kubebuilder:printcolumn:name="Type",type=string,JSONPath=`.spec.type` // +kubebuilder:printcolumn:name="Device",type=string,JSONPath=`.spec.deviceRef.name` // +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" diff --git a/api/core/v1alpha1/bgp_types.go b/api/core/v1alpha1/bgp_types.go index 15c509116..32aa66dff 100644 --- a/api/core/v1alpha1/bgp_types.go +++ b/api/core/v1alpha1/bgp_types.go @@ -182,6 +182,7 @@ type BGPStatus struct { // +kubebuilder:printcolumn:name="Device",type=string,JSONPath=`.spec.deviceRef.name` // +kubebuilder:printcolumn:name="Admin State",type=string,JSONPath=`.spec.adminState` // +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" diff --git a/api/core/v1alpha1/certificate_types.go b/api/core/v1alpha1/certificate_types.go index 5d0093d79..042bf4644 100644 --- a/api/core/v1alpha1/certificate_types.go +++ b/api/core/v1alpha1/certificate_types.go @@ -57,6 +57,7 @@ type CertificateStatus struct { // +kubebuilder:printcolumn:name="Certificate",type=string,JSONPath=`.spec.id` // +kubebuilder:printcolumn:name="Device",type=string,JSONPath=`.spec.deviceRef.name` // +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" diff --git a/api/core/v1alpha1/dhcprelay_types.go b/api/core/v1alpha1/dhcprelay_types.go index a418efd8f..21ff93172 100644 --- a/api/core/v1alpha1/dhcprelay_types.go +++ b/api/core/v1alpha1/dhcprelay_types.go @@ -75,6 +75,7 @@ type DHCPRelayStatus struct { // +kubebuilder:resource:singular=dhcprelay // +kubebuilder:printcolumn:name="Device",type=string,JSONPath=`.spec.deviceRef.name` // +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="Age",type="date",JSONPath=".metadata.creationTimestamp" // DHCPRelay is the Schema for the DHCPRelays API diff --git a/api/core/v1alpha1/dns_types.go b/api/core/v1alpha1/dns_types.go index 509579acf..0cb9f1747 100644 --- a/api/core/v1alpha1/dns_types.go +++ b/api/core/v1alpha1/dns_types.go @@ -83,6 +83,7 @@ type DNSStatus struct { // +kubebuilder:printcolumn:name="Device",type=string,JSONPath=`.spec.deviceRef.name` // +kubebuilder:printcolumn:name="Admin State",type=string,JSONPath=`.spec.adminState` // +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" diff --git a/api/core/v1alpha1/evpninstance_types.go b/api/core/v1alpha1/evpninstance_types.go index d38fb01b7..75a1f62fd 100644 --- a/api/core/v1alpha1/evpninstance_types.go +++ b/api/core/v1alpha1/evpninstance_types.go @@ -133,6 +133,7 @@ type EVPNInstanceStatus struct { // +kubebuilder:printcolumn:name="Type",type=string,JSONPath=`.spec.type` // +kubebuilder:printcolumn:name="Route Distinguisher",type=string,JSONPath=`.spec.routeDistinguisher`,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" diff --git a/api/core/v1alpha1/isis_types.go b/api/core/v1alpha1/isis_types.go index 3a3c17da2..30165e3ef 100644 --- a/api/core/v1alpha1/isis_types.go +++ b/api/core/v1alpha1/isis_types.go @@ -112,6 +112,7 @@ type ISISStatus struct { // +kubebuilder:printcolumn:name="NET",type=string,JSONPath=`.spec.networkEntityTitle` // +kubebuilder:printcolumn:name="Level",type=string,JSONPath=`.spec.type`,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" diff --git a/api/core/v1alpha1/managementaccess_types.go b/api/core/v1alpha1/managementaccess_types.go index 8f5473bfd..4cad4945b 100644 --- a/api/core/v1alpha1/managementaccess_types.go +++ b/api/core/v1alpha1/managementaccess_types.go @@ -140,6 +140,7 @@ type ManagementAccessStatus struct { // +kubebuilder:printcolumn:name="gRPC",type=boolean,JSONPath=`.spec.grpc.enabled` // +kubebuilder:printcolumn:name="gRPC Port",type=integer,JSONPath=`.spec.grpc.port`,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" diff --git a/api/core/v1alpha1/ntp_types.go b/api/core/v1alpha1/ntp_types.go index f6ffaddc9..bd7cc20cd 100644 --- a/api/core/v1alpha1/ntp_types.go +++ b/api/core/v1alpha1/ntp_types.go @@ -80,6 +80,7 @@ type NTPStatus struct { // +kubebuilder:printcolumn:name="Admin State",type=string,JSONPath=`.spec.adminState` // +kubebuilder:printcolumn:name="Source Interface",type=string,JSONPath=`.spec.sourceInterfaceName` // +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" diff --git a/api/core/v1alpha1/pim_types.go b/api/core/v1alpha1/pim_types.go index e3ed5e299..5acd27c26 100644 --- a/api/core/v1alpha1/pim_types.go +++ b/api/core/v1alpha1/pim_types.go @@ -96,6 +96,7 @@ type PIMStatus struct { // +kubebuilder:printcolumn:name="Device",type=string,JSONPath=`.spec.deviceRef.name` // +kubebuilder:printcolumn:name="Admin State",type=string,JSONPath=`.spec.adminState` // +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" diff --git a/api/core/v1alpha1/prefixset_types.go b/api/core/v1alpha1/prefixset_types.go index f80d433ce..2ec64f895 100644 --- a/api/core/v1alpha1/prefixset_types.go +++ b/api/core/v1alpha1/prefixset_types.go @@ -102,6 +102,7 @@ type PrefixSetStatus 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" diff --git a/api/core/v1alpha1/routingpolicy_types.go b/api/core/v1alpha1/routingpolicy_types.go index da6ba9366..da9c9b5dc 100644 --- a/api/core/v1alpha1/routingpolicy_types.go +++ b/api/core/v1alpha1/routingpolicy_types.go @@ -209,6 +209,7 @@ type RoutingPolicyStatus struct { // +kubebuilder:printcolumn:name="Device",type=string,JSONPath=`.spec.deviceRef.name` // +kubebuilder:printcolumn:name="Statements",type=string,JSONPath=`.status.statementsSummary`,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" diff --git a/api/core/v1alpha1/snmp_types.go b/api/core/v1alpha1/snmp_types.go index 47dabca9c..4f51046b1 100644 --- a/api/core/v1alpha1/snmp_types.go +++ b/api/core/v1alpha1/snmp_types.go @@ -134,6 +134,7 @@ type SNMPStatus struct { // +kubebuilder:printcolumn:name="Device",type=string,JSONPath=`.spec.deviceRef.name` // +kubebuilder:printcolumn:name="Source Interface",type=string,JSONPath=`.spec.sourceInterfaceName` // +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" diff --git a/api/core/v1alpha1/syslog_types.go b/api/core/v1alpha1/syslog_types.go index ad4de53cb..5b7fd6b9c 100644 --- a/api/core/v1alpha1/syslog_types.go +++ b/api/core/v1alpha1/syslog_types.go @@ -110,6 +110,7 @@ type SyslogStatus struct { // +kubebuilder:printcolumn:name="Device",type=string,JSONPath=`.spec.deviceRef.name` // +kubebuilder:printcolumn:name="Servers",type=string,JSONPath=`.status.serversSummary`,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" diff --git a/api/core/v1alpha1/user_types.go b/api/core/v1alpha1/user_types.go index 5f2b05f02..85a43d154 100644 --- a/api/core/v1alpha1/user_types.go +++ b/api/core/v1alpha1/user_types.go @@ -90,6 +90,7 @@ type UserStatus struct { // +kubebuilder:printcolumn:name="Username",type=string,JSONPath=`.spec.username` // +kubebuilder:printcolumn:name="Device",type=string,JSONPath=`.spec.deviceRef.name` // +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" diff --git a/api/core/v1alpha1/vrf_types.go b/api/core/v1alpha1/vrf_types.go index af9d125f3..f5fd9e9a3 100644 --- a/api/core/v1alpha1/vrf_types.go +++ b/api/core/v1alpha1/vrf_types.go @@ -127,6 +127,7 @@ type VRFStatus struct { // +kubebuilder:printcolumn:name="Device",type=string,JSONPath=`.spec.deviceRef.name` // +kubebuilder:printcolumn:name="Route Distinguisher",type=string,JSONPath=`.spec.routeDistinguisher`,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" diff --git a/charts/network-operator/templates/crd/aaa.networking.metal.ironcore.dev.yaml b/charts/network-operator/templates/crd/aaa.networking.metal.ironcore.dev.yaml index 6618110cb..7a1f1d2b4 100644 --- a/charts/network-operator/templates/crd/aaa.networking.metal.ironcore.dev.yaml +++ b/charts/network-operator/templates/crd/aaa.networking.metal.ironcore.dev.yaml @@ -24,6 +24,10 @@ spec: - jsonPath: .status.conditions[?(@.type=="Ready")].status name: Ready type: string + - jsonPath: .status.conditions[?(@.type=="Configured")].status + name: Configured + priority: 1 + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date diff --git a/charts/network-operator/templates/crd/accesscontrollists.networking.metal.ironcore.dev.yaml b/charts/network-operator/templates/crd/accesscontrollists.networking.metal.ironcore.dev.yaml index 522a48dc4..8a6e0103a 100644 --- a/charts/network-operator/templates/crd/accesscontrollists.networking.metal.ironcore.dev.yaml +++ b/charts/network-operator/templates/crd/accesscontrollists.networking.metal.ironcore.dev.yaml @@ -33,6 +33,10 @@ spec: - jsonPath: .status.conditions[?(@.type=="Ready")].status name: Ready type: string + - jsonPath: .status.conditions[?(@.type=="Configured")].status + name: Configured + priority: 1 + type: string - jsonPath: .status.conditions[?(@.type=="Paused")].status name: Paused priority: 1 diff --git a/charts/network-operator/templates/crd/banners.networking.metal.ironcore.dev.yaml b/charts/network-operator/templates/crd/banners.networking.metal.ironcore.dev.yaml index 6de5a6b63..ff72bc336 100644 --- a/charts/network-operator/templates/crd/banners.networking.metal.ironcore.dev.yaml +++ b/charts/network-operator/templates/crd/banners.networking.metal.ironcore.dev.yaml @@ -27,6 +27,10 @@ spec: - jsonPath: .status.conditions[?(@.type=="Ready")].status name: Ready type: string + - jsonPath: .status.conditions[?(@.type=="Configured")].status + name: Configured + priority: 1 + type: string - jsonPath: .status.conditions[?(@.type=="Paused")].status name: Paused priority: 1 diff --git a/charts/network-operator/templates/crd/bgp.networking.metal.ironcore.dev.yaml b/charts/network-operator/templates/crd/bgp.networking.metal.ironcore.dev.yaml index 8f00271f0..c778f5b4e 100644 --- a/charts/network-operator/templates/crd/bgp.networking.metal.ironcore.dev.yaml +++ b/charts/network-operator/templates/crd/bgp.networking.metal.ironcore.dev.yaml @@ -33,6 +33,10 @@ spec: - jsonPath: .status.conditions[?(@.type=="Ready")].status name: Ready type: string + - jsonPath: .status.conditions[?(@.type=="Configured")].status + name: Configured + priority: 1 + type: string - jsonPath: .status.conditions[?(@.type=="Paused")].status name: Paused priority: 1 diff --git a/charts/network-operator/templates/crd/bordergateways.nx.cisco.networking.metal.ironcore.dev.yaml b/charts/network-operator/templates/crd/bordergateways.nx.cisco.networking.metal.ironcore.dev.yaml index 3f5d964f0..a422ea6fe 100644 --- a/charts/network-operator/templates/crd/bordergateways.nx.cisco.networking.metal.ironcore.dev.yaml +++ b/charts/network-operator/templates/crd/bordergateways.nx.cisco.networking.metal.ironcore.dev.yaml @@ -35,6 +35,10 @@ spec: - jsonPath: .status.conditions[?(@.type=="Ready")].status name: Ready type: string + - jsonPath: .status.conditions[?(@.type=="Configured")].status + name: Configured + priority: 1 + type: string - jsonPath: .status.conditions[?(@.type=="Paused")].status name: Paused priority: 1 diff --git a/charts/network-operator/templates/crd/certificates.networking.metal.ironcore.dev.yaml b/charts/network-operator/templates/crd/certificates.networking.metal.ironcore.dev.yaml index 131841c66..dc891f7cf 100644 --- a/charts/network-operator/templates/crd/certificates.networking.metal.ironcore.dev.yaml +++ b/charts/network-operator/templates/crd/certificates.networking.metal.ironcore.dev.yaml @@ -30,6 +30,10 @@ spec: - jsonPath: .status.conditions[?(@.type=="Ready")].status name: Ready type: string + - jsonPath: .status.conditions[?(@.type=="Configured")].status + name: Configured + priority: 1 + type: string - jsonPath: .status.conditions[?(@.type=="Paused")].status name: Paused priority: 1 diff --git a/charts/network-operator/templates/crd/dhcprelays.networking.metal.ironcore.dev.yaml b/charts/network-operator/templates/crd/dhcprelays.networking.metal.ironcore.dev.yaml index e6b75612b..3d80d5150 100644 --- a/charts/network-operator/templates/crd/dhcprelays.networking.metal.ironcore.dev.yaml +++ b/charts/network-operator/templates/crd/dhcprelays.networking.metal.ironcore.dev.yaml @@ -24,6 +24,10 @@ spec: - jsonPath: .status.conditions[?(@.type=="Ready")].status name: Ready type: string + - jsonPath: .status.conditions[?(@.type=="Configured")].status + name: Configured + priority: 1 + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date diff --git a/charts/network-operator/templates/crd/dns.networking.metal.ironcore.dev.yaml b/charts/network-operator/templates/crd/dns.networking.metal.ironcore.dev.yaml index 13627661f..ec90afc26 100644 --- a/charts/network-operator/templates/crd/dns.networking.metal.ironcore.dev.yaml +++ b/charts/network-operator/templates/crd/dns.networking.metal.ironcore.dev.yaml @@ -30,6 +30,10 @@ spec: - jsonPath: .status.conditions[?(@.type=="Ready")].status name: Ready type: string + - jsonPath: .status.conditions[?(@.type=="Configured")].status + name: Configured + priority: 1 + type: string - jsonPath: .status.conditions[?(@.type=="Paused")].status name: Paused priority: 1 diff --git a/charts/network-operator/templates/crd/evpninstances.networking.metal.ironcore.dev.yaml b/charts/network-operator/templates/crd/evpninstances.networking.metal.ironcore.dev.yaml index 519d3d2d4..bbea74a83 100644 --- a/charts/network-operator/templates/crd/evpninstances.networking.metal.ironcore.dev.yaml +++ b/charts/network-operator/templates/crd/evpninstances.networking.metal.ironcore.dev.yaml @@ -37,6 +37,10 @@ spec: - jsonPath: .status.conditions[?(@.type=="Ready")].status name: Ready type: string + - jsonPath: .status.conditions[?(@.type=="Configured")].status + name: Configured + priority: 1 + type: string - jsonPath: .status.conditions[?(@.type=="Paused")].status name: Paused priority: 1 diff --git a/charts/network-operator/templates/crd/isis.networking.metal.ironcore.dev.yaml b/charts/network-operator/templates/crd/isis.networking.metal.ironcore.dev.yaml index 29ed18f46..f4529f6db 100644 --- a/charts/network-operator/templates/crd/isis.networking.metal.ironcore.dev.yaml +++ b/charts/network-operator/templates/crd/isis.networking.metal.ironcore.dev.yaml @@ -37,6 +37,10 @@ spec: - jsonPath: .status.conditions[?(@.type=="Ready")].status name: Ready type: string + - jsonPath: .status.conditions[?(@.type=="Configured")].status + name: Configured + priority: 1 + type: string - jsonPath: .status.conditions[?(@.type=="Paused")].status name: Paused priority: 1 diff --git a/charts/network-operator/templates/crd/managementaccesses.networking.metal.ironcore.dev.yaml b/charts/network-operator/templates/crd/managementaccesses.networking.metal.ironcore.dev.yaml index 22c23c81f..99527c9ed 100644 --- a/charts/network-operator/templates/crd/managementaccesses.networking.metal.ironcore.dev.yaml +++ b/charts/network-operator/templates/crd/managementaccesses.networking.metal.ironcore.dev.yaml @@ -37,6 +37,10 @@ spec: - jsonPath: .status.conditions[?(@.type=="Ready")].status name: Ready type: string + - jsonPath: .status.conditions[?(@.type=="Configured")].status + name: Configured + priority: 1 + type: string - jsonPath: .status.conditions[?(@.type=="Paused")].status name: Paused priority: 1 diff --git a/charts/network-operator/templates/crd/ntp.networking.metal.ironcore.dev.yaml b/charts/network-operator/templates/crd/ntp.networking.metal.ironcore.dev.yaml index caf783013..7453bdb82 100644 --- a/charts/network-operator/templates/crd/ntp.networking.metal.ironcore.dev.yaml +++ b/charts/network-operator/templates/crd/ntp.networking.metal.ironcore.dev.yaml @@ -30,6 +30,10 @@ spec: - jsonPath: .status.conditions[?(@.type=="Ready")].status name: Ready type: string + - jsonPath: .status.conditions[?(@.type=="Configured")].status + name: Configured + priority: 1 + type: string - jsonPath: .status.conditions[?(@.type=="Paused")].status name: Paused priority: 1 diff --git a/charts/network-operator/templates/crd/pim.networking.metal.ironcore.dev.yaml b/charts/network-operator/templates/crd/pim.networking.metal.ironcore.dev.yaml index 31b2dbc30..23d2dca9a 100644 --- a/charts/network-operator/templates/crd/pim.networking.metal.ironcore.dev.yaml +++ b/charts/network-operator/templates/crd/pim.networking.metal.ironcore.dev.yaml @@ -27,6 +27,10 @@ spec: - jsonPath: .status.conditions[?(@.type=="Ready")].status name: Ready type: string + - jsonPath: .status.conditions[?(@.type=="Configured")].status + name: Configured + priority: 1 + type: string - jsonPath: .status.conditions[?(@.type=="Paused")].status name: Paused priority: 1 diff --git a/charts/network-operator/templates/crd/prefixsets.networking.metal.ironcore.dev.yaml b/charts/network-operator/templates/crd/prefixsets.networking.metal.ironcore.dev.yaml index 483e400fe..715109f7a 100644 --- a/charts/network-operator/templates/crd/prefixsets.networking.metal.ironcore.dev.yaml +++ b/charts/network-operator/templates/crd/prefixsets.networking.metal.ironcore.dev.yaml @@ -31,6 +31,10 @@ spec: - jsonPath: .status.conditions[?(@.type=="Ready")].status name: Ready type: string + - jsonPath: .status.conditions[?(@.type=="Configured")].status + name: Configured + priority: 1 + type: string - jsonPath: .status.conditions[?(@.type=="Paused")].status name: Paused priority: 1 diff --git a/charts/network-operator/templates/crd/routingpolicies.networking.metal.ironcore.dev.yaml b/charts/network-operator/templates/crd/routingpolicies.networking.metal.ironcore.dev.yaml index e35f4c87c..70913edc2 100644 --- a/charts/network-operator/templates/crd/routingpolicies.networking.metal.ironcore.dev.yaml +++ b/charts/network-operator/templates/crd/routingpolicies.networking.metal.ironcore.dev.yaml @@ -33,6 +33,10 @@ spec: - jsonPath: .status.conditions[?(@.type=="Ready")].status name: Ready type: string + - jsonPath: .status.conditions[?(@.type=="Configured")].status + name: Configured + priority: 1 + type: string - jsonPath: .status.conditions[?(@.type=="Paused")].status name: Paused priority: 1 diff --git a/charts/network-operator/templates/crd/snmp.networking.metal.ironcore.dev.yaml b/charts/network-operator/templates/crd/snmp.networking.metal.ironcore.dev.yaml index e387619a6..7cd403e25 100644 --- a/charts/network-operator/templates/crd/snmp.networking.metal.ironcore.dev.yaml +++ b/charts/network-operator/templates/crd/snmp.networking.metal.ironcore.dev.yaml @@ -27,6 +27,10 @@ spec: - jsonPath: .status.conditions[?(@.type=="Ready")].status name: Ready type: string + - jsonPath: .status.conditions[?(@.type=="Configured")].status + name: Configured + priority: 1 + type: string - jsonPath: .status.conditions[?(@.type=="Paused")].status name: Paused priority: 1 diff --git a/charts/network-operator/templates/crd/syslogs.networking.metal.ironcore.dev.yaml b/charts/network-operator/templates/crd/syslogs.networking.metal.ironcore.dev.yaml index c2aaec59f..8fba96d0f 100644 --- a/charts/network-operator/templates/crd/syslogs.networking.metal.ironcore.dev.yaml +++ b/charts/network-operator/templates/crd/syslogs.networking.metal.ironcore.dev.yaml @@ -28,6 +28,10 @@ spec: - jsonPath: .status.conditions[?(@.type=="Ready")].status name: Ready type: string + - jsonPath: .status.conditions[?(@.type=="Configured")].status + name: Configured + priority: 1 + type: string - jsonPath: .status.conditions[?(@.type=="Paused")].status name: Paused priority: 1 diff --git a/charts/network-operator/templates/crd/systems.nx.cisco.networking.metal.ironcore.dev.yaml b/charts/network-operator/templates/crd/systems.nx.cisco.networking.metal.ironcore.dev.yaml index d7d9c2661..5acc30ffb 100644 --- a/charts/network-operator/templates/crd/systems.nx.cisco.networking.metal.ironcore.dev.yaml +++ b/charts/network-operator/templates/crd/systems.nx.cisco.networking.metal.ironcore.dev.yaml @@ -26,6 +26,10 @@ spec: - jsonPath: .status.conditions[?(@.type=="Ready")].status name: Ready type: string + - jsonPath: .status.conditions[?(@.type=="Configured")].status + name: Configured + priority: 1 + type: string - jsonPath: .status.conditions[?(@.type=="Paused")].status name: Paused priority: 1 diff --git a/charts/network-operator/templates/crd/users.networking.metal.ironcore.dev.yaml b/charts/network-operator/templates/crd/users.networking.metal.ironcore.dev.yaml index ceb20bd9c..93571de9c 100644 --- a/charts/network-operator/templates/crd/users.networking.metal.ironcore.dev.yaml +++ b/charts/network-operator/templates/crd/users.networking.metal.ironcore.dev.yaml @@ -27,6 +27,10 @@ spec: - jsonPath: .status.conditions[?(@.type=="Ready")].status name: Ready type: string + - jsonPath: .status.conditions[?(@.type=="Configured")].status + name: Configured + priority: 1 + type: string - jsonPath: .status.conditions[?(@.type=="Paused")].status name: Paused priority: 1 diff --git a/charts/network-operator/templates/crd/vrfs.networking.metal.ironcore.dev.yaml b/charts/network-operator/templates/crd/vrfs.networking.metal.ironcore.dev.yaml index c377a8eb3..a0e4f778c 100644 --- a/charts/network-operator/templates/crd/vrfs.networking.metal.ironcore.dev.yaml +++ b/charts/network-operator/templates/crd/vrfs.networking.metal.ironcore.dev.yaml @@ -31,6 +31,10 @@ spec: - jsonPath: .status.conditions[?(@.type=="Ready")].status name: Ready type: string + - jsonPath: .status.conditions[?(@.type=="Configured")].status + name: Configured + priority: 1 + type: string - jsonPath: .status.conditions[?(@.type=="Paused")].status name: Paused priority: 1 diff --git a/config/crd/bases/networking.metal.ironcore.dev_aaa.yaml b/config/crd/bases/networking.metal.ironcore.dev_aaa.yaml index 1f03feb52..e8c01aa4c 100644 --- a/config/crd/bases/networking.metal.ironcore.dev_aaa.yaml +++ b/config/crd/bases/networking.metal.ironcore.dev_aaa.yaml @@ -21,6 +21,10 @@ spec: - jsonPath: .status.conditions[?(@.type=="Ready")].status name: Ready type: string + - jsonPath: .status.conditions[?(@.type=="Configured")].status + name: Configured + priority: 1 + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date diff --git a/config/crd/bases/networking.metal.ironcore.dev_accesscontrollists.yaml b/config/crd/bases/networking.metal.ironcore.dev_accesscontrollists.yaml index 39480b6f7..7a431766b 100644 --- a/config/crd/bases/networking.metal.ironcore.dev_accesscontrollists.yaml +++ b/config/crd/bases/networking.metal.ironcore.dev_accesscontrollists.yaml @@ -30,6 +30,10 @@ spec: - jsonPath: .status.conditions[?(@.type=="Ready")].status name: Ready type: string + - jsonPath: .status.conditions[?(@.type=="Configured")].status + name: Configured + priority: 1 + type: string - jsonPath: .status.conditions[?(@.type=="Paused")].status name: Paused priority: 1 diff --git a/config/crd/bases/networking.metal.ironcore.dev_banners.yaml b/config/crd/bases/networking.metal.ironcore.dev_banners.yaml index 009b9edba..467e8466e 100644 --- a/config/crd/bases/networking.metal.ironcore.dev_banners.yaml +++ b/config/crd/bases/networking.metal.ironcore.dev_banners.yaml @@ -24,6 +24,10 @@ spec: - jsonPath: .status.conditions[?(@.type=="Ready")].status name: Ready type: string + - jsonPath: .status.conditions[?(@.type=="Configured")].status + name: Configured + priority: 1 + type: string - jsonPath: .status.conditions[?(@.type=="Paused")].status name: Paused priority: 1 diff --git a/config/crd/bases/networking.metal.ironcore.dev_bgp.yaml b/config/crd/bases/networking.metal.ironcore.dev_bgp.yaml index 8a1780f75..f7710f095 100644 --- a/config/crd/bases/networking.metal.ironcore.dev_bgp.yaml +++ b/config/crd/bases/networking.metal.ironcore.dev_bgp.yaml @@ -30,6 +30,10 @@ spec: - jsonPath: .status.conditions[?(@.type=="Ready")].status name: Ready type: string + - jsonPath: .status.conditions[?(@.type=="Configured")].status + name: Configured + priority: 1 + type: string - jsonPath: .status.conditions[?(@.type=="Paused")].status name: Paused priority: 1 diff --git a/config/crd/bases/networking.metal.ironcore.dev_certificates.yaml b/config/crd/bases/networking.metal.ironcore.dev_certificates.yaml index 17bfe94ab..c33a338d5 100644 --- a/config/crd/bases/networking.metal.ironcore.dev_certificates.yaml +++ b/config/crd/bases/networking.metal.ironcore.dev_certificates.yaml @@ -27,6 +27,10 @@ spec: - jsonPath: .status.conditions[?(@.type=="Ready")].status name: Ready type: string + - jsonPath: .status.conditions[?(@.type=="Configured")].status + name: Configured + priority: 1 + type: string - jsonPath: .status.conditions[?(@.type=="Paused")].status name: Paused priority: 1 diff --git a/config/crd/bases/networking.metal.ironcore.dev_dhcprelays.yaml b/config/crd/bases/networking.metal.ironcore.dev_dhcprelays.yaml index 48cefc42c..44b9f2232 100644 --- a/config/crd/bases/networking.metal.ironcore.dev_dhcprelays.yaml +++ b/config/crd/bases/networking.metal.ironcore.dev_dhcprelays.yaml @@ -21,6 +21,10 @@ spec: - jsonPath: .status.conditions[?(@.type=="Ready")].status name: Ready type: string + - jsonPath: .status.conditions[?(@.type=="Configured")].status + name: Configured + priority: 1 + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date diff --git a/config/crd/bases/networking.metal.ironcore.dev_dns.yaml b/config/crd/bases/networking.metal.ironcore.dev_dns.yaml index 9b66a3a11..7401504a8 100644 --- a/config/crd/bases/networking.metal.ironcore.dev_dns.yaml +++ b/config/crd/bases/networking.metal.ironcore.dev_dns.yaml @@ -27,6 +27,10 @@ spec: - jsonPath: .status.conditions[?(@.type=="Ready")].status name: Ready type: string + - jsonPath: .status.conditions[?(@.type=="Configured")].status + name: Configured + priority: 1 + type: string - jsonPath: .status.conditions[?(@.type=="Paused")].status name: Paused priority: 1 diff --git a/config/crd/bases/networking.metal.ironcore.dev_evpninstances.yaml b/config/crd/bases/networking.metal.ironcore.dev_evpninstances.yaml index 8ee981b5f..b2e8f4b67 100644 --- a/config/crd/bases/networking.metal.ironcore.dev_evpninstances.yaml +++ b/config/crd/bases/networking.metal.ironcore.dev_evpninstances.yaml @@ -34,6 +34,10 @@ spec: - jsonPath: .status.conditions[?(@.type=="Ready")].status name: Ready type: string + - jsonPath: .status.conditions[?(@.type=="Configured")].status + name: Configured + priority: 1 + type: string - jsonPath: .status.conditions[?(@.type=="Paused")].status name: Paused priority: 1 diff --git a/config/crd/bases/networking.metal.ironcore.dev_isis.yaml b/config/crd/bases/networking.metal.ironcore.dev_isis.yaml index 00428b26b..91341fc8d 100644 --- a/config/crd/bases/networking.metal.ironcore.dev_isis.yaml +++ b/config/crd/bases/networking.metal.ironcore.dev_isis.yaml @@ -34,6 +34,10 @@ spec: - jsonPath: .status.conditions[?(@.type=="Ready")].status name: Ready type: string + - jsonPath: .status.conditions[?(@.type=="Configured")].status + name: Configured + priority: 1 + type: string - jsonPath: .status.conditions[?(@.type=="Paused")].status name: Paused priority: 1 diff --git a/config/crd/bases/networking.metal.ironcore.dev_managementaccesses.yaml b/config/crd/bases/networking.metal.ironcore.dev_managementaccesses.yaml index 94ffbe6ab..5802f5f3d 100644 --- a/config/crd/bases/networking.metal.ironcore.dev_managementaccesses.yaml +++ b/config/crd/bases/networking.metal.ironcore.dev_managementaccesses.yaml @@ -34,6 +34,10 @@ spec: - jsonPath: .status.conditions[?(@.type=="Ready")].status name: Ready type: string + - jsonPath: .status.conditions[?(@.type=="Configured")].status + name: Configured + priority: 1 + type: string - jsonPath: .status.conditions[?(@.type=="Paused")].status name: Paused priority: 1 diff --git a/config/crd/bases/networking.metal.ironcore.dev_ntp.yaml b/config/crd/bases/networking.metal.ironcore.dev_ntp.yaml index 184698a73..d4dc2e6c0 100644 --- a/config/crd/bases/networking.metal.ironcore.dev_ntp.yaml +++ b/config/crd/bases/networking.metal.ironcore.dev_ntp.yaml @@ -27,6 +27,10 @@ spec: - jsonPath: .status.conditions[?(@.type=="Ready")].status name: Ready type: string + - jsonPath: .status.conditions[?(@.type=="Configured")].status + name: Configured + priority: 1 + type: string - jsonPath: .status.conditions[?(@.type=="Paused")].status name: Paused priority: 1 diff --git a/config/crd/bases/networking.metal.ironcore.dev_pim.yaml b/config/crd/bases/networking.metal.ironcore.dev_pim.yaml index 1851dcc72..80688d95f 100644 --- a/config/crd/bases/networking.metal.ironcore.dev_pim.yaml +++ b/config/crd/bases/networking.metal.ironcore.dev_pim.yaml @@ -24,6 +24,10 @@ spec: - jsonPath: .status.conditions[?(@.type=="Ready")].status name: Ready type: string + - jsonPath: .status.conditions[?(@.type=="Configured")].status + name: Configured + priority: 1 + type: string - jsonPath: .status.conditions[?(@.type=="Paused")].status name: Paused priority: 1 diff --git a/config/crd/bases/networking.metal.ironcore.dev_prefixsets.yaml b/config/crd/bases/networking.metal.ironcore.dev_prefixsets.yaml index 71fd6800c..dddd3e146 100644 --- a/config/crd/bases/networking.metal.ironcore.dev_prefixsets.yaml +++ b/config/crd/bases/networking.metal.ironcore.dev_prefixsets.yaml @@ -28,6 +28,10 @@ spec: - jsonPath: .status.conditions[?(@.type=="Ready")].status name: Ready type: string + - jsonPath: .status.conditions[?(@.type=="Configured")].status + name: Configured + priority: 1 + type: string - jsonPath: .status.conditions[?(@.type=="Paused")].status name: Paused priority: 1 diff --git a/config/crd/bases/networking.metal.ironcore.dev_routingpolicies.yaml b/config/crd/bases/networking.metal.ironcore.dev_routingpolicies.yaml index ac932b49b..669f7fb1a 100644 --- a/config/crd/bases/networking.metal.ironcore.dev_routingpolicies.yaml +++ b/config/crd/bases/networking.metal.ironcore.dev_routingpolicies.yaml @@ -30,6 +30,10 @@ spec: - jsonPath: .status.conditions[?(@.type=="Ready")].status name: Ready type: string + - jsonPath: .status.conditions[?(@.type=="Configured")].status + name: Configured + priority: 1 + type: string - jsonPath: .status.conditions[?(@.type=="Paused")].status name: Paused priority: 1 diff --git a/config/crd/bases/networking.metal.ironcore.dev_snmp.yaml b/config/crd/bases/networking.metal.ironcore.dev_snmp.yaml index 062e4955a..5e6c685c6 100644 --- a/config/crd/bases/networking.metal.ironcore.dev_snmp.yaml +++ b/config/crd/bases/networking.metal.ironcore.dev_snmp.yaml @@ -24,6 +24,10 @@ spec: - jsonPath: .status.conditions[?(@.type=="Ready")].status name: Ready type: string + - jsonPath: .status.conditions[?(@.type=="Configured")].status + name: Configured + priority: 1 + type: string - jsonPath: .status.conditions[?(@.type=="Paused")].status name: Paused priority: 1 diff --git a/config/crd/bases/networking.metal.ironcore.dev_syslogs.yaml b/config/crd/bases/networking.metal.ironcore.dev_syslogs.yaml index 114680bfe..cc6ce41a4 100644 --- a/config/crd/bases/networking.metal.ironcore.dev_syslogs.yaml +++ b/config/crd/bases/networking.metal.ironcore.dev_syslogs.yaml @@ -25,6 +25,10 @@ spec: - jsonPath: .status.conditions[?(@.type=="Ready")].status name: Ready type: string + - jsonPath: .status.conditions[?(@.type=="Configured")].status + name: Configured + priority: 1 + type: string - jsonPath: .status.conditions[?(@.type=="Paused")].status name: Paused priority: 1 diff --git a/config/crd/bases/networking.metal.ironcore.dev_users.yaml b/config/crd/bases/networking.metal.ironcore.dev_users.yaml index e0efe19f0..8bc8d0447 100644 --- a/config/crd/bases/networking.metal.ironcore.dev_users.yaml +++ b/config/crd/bases/networking.metal.ironcore.dev_users.yaml @@ -24,6 +24,10 @@ spec: - jsonPath: .status.conditions[?(@.type=="Ready")].status name: Ready type: string + - jsonPath: .status.conditions[?(@.type=="Configured")].status + name: Configured + priority: 1 + type: string - jsonPath: .status.conditions[?(@.type=="Paused")].status name: Paused priority: 1 diff --git a/config/crd/bases/networking.metal.ironcore.dev_vrfs.yaml b/config/crd/bases/networking.metal.ironcore.dev_vrfs.yaml index 535cd7b04..7695980aa 100644 --- a/config/crd/bases/networking.metal.ironcore.dev_vrfs.yaml +++ b/config/crd/bases/networking.metal.ironcore.dev_vrfs.yaml @@ -28,6 +28,10 @@ spec: - jsonPath: .status.conditions[?(@.type=="Ready")].status name: Ready type: string + - jsonPath: .status.conditions[?(@.type=="Configured")].status + name: Configured + priority: 1 + type: string - jsonPath: .status.conditions[?(@.type=="Paused")].status name: Paused priority: 1 diff --git a/config/crd/bases/nx.cisco.networking.metal.ironcore.dev_bordergateways.yaml b/config/crd/bases/nx.cisco.networking.metal.ironcore.dev_bordergateways.yaml index 54496856e..4c2aac415 100644 --- a/config/crd/bases/nx.cisco.networking.metal.ironcore.dev_bordergateways.yaml +++ b/config/crd/bases/nx.cisco.networking.metal.ironcore.dev_bordergateways.yaml @@ -32,6 +32,10 @@ spec: - jsonPath: .status.conditions[?(@.type=="Ready")].status name: Ready type: string + - jsonPath: .status.conditions[?(@.type=="Configured")].status + name: Configured + priority: 1 + type: string - jsonPath: .status.conditions[?(@.type=="Paused")].status name: Paused priority: 1 diff --git a/config/crd/bases/nx.cisco.networking.metal.ironcore.dev_systems.yaml b/config/crd/bases/nx.cisco.networking.metal.ironcore.dev_systems.yaml index 2a03f32b8..5b39f3390 100644 --- a/config/crd/bases/nx.cisco.networking.metal.ironcore.dev_systems.yaml +++ b/config/crd/bases/nx.cisco.networking.metal.ironcore.dev_systems.yaml @@ -23,6 +23,10 @@ spec: - jsonPath: .status.conditions[?(@.type=="Ready")].status name: Ready type: string + - jsonPath: .status.conditions[?(@.type=="Configured")].status + name: Configured + priority: 1 + type: string - jsonPath: .status.conditions[?(@.type=="Paused")].status name: Paused priority: 1 diff --git a/internal/controller/cisco/nx/bordergateway_controller.go b/internal/controller/cisco/nx/bordergateway_controller.go index 1e9b2324f..44d77dae1 100644 --- a/internal/controller/cisco/nx/bordergateway_controller.go +++ b/internal/controller/cisco/nx/bordergateway_controller.go @@ -165,7 +165,7 @@ func (r *BorderGatewayReconciler) Reconcile(ctx context.Context, req ctrl.Reques } orig := obj.DeepCopy() - if conditions.InitializeConditions(obj, v1alpha1.ReadyCondition) { + if conditions.InitializeConditions(obj, v1alpha1.ReadyCondition, v1alpha1.ConfiguredCondition) { log.V(1).Info("Initializing status conditions") return ctrl.Result{}, r.Status().Update(ctx, obj) } @@ -327,6 +327,10 @@ func (r *BorderGatewayReconciler) reconcile(ctx context.Context, s *borderGatewa s.BorderGateway.Labels[v1alpha1.DeviceLabel] = s.Device.Name + defer func() { + conditions.RecomputeReady(s.BorderGateway) + }() + // Ensure the BorderGateway is owned by the Device. if !controllerutil.HasControllerReference(s.BorderGateway) { if err := controllerutil.SetOwnerReference(s.Device, s.BorderGateway, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { @@ -367,8 +371,6 @@ func (r *BorderGatewayReconciler) reconcile(ctx context.Context, s *borderGatewa }) cond := conditions.FromError(err) - // As this resource is configuration only, we use the Configured condition as top-level Ready condition. - cond.Type = v1alpha1.ReadyCondition conditions.Set(s.BorderGateway, cond) return err @@ -386,7 +388,7 @@ func (r *BorderGatewayReconciler) reconcileSourceInterface(ctx context.Context, if err := r.Get(ctx, key, intf); err != nil { if apierrors.IsNotFound(err) { conditions.Set(s.BorderGateway, metav1.Condition{ - Type: v1alpha1.ReadyCondition, + Type: v1alpha1.ConfiguredCondition, Status: metav1.ConditionFalse, Reason: v1alpha1.InterfaceNotFoundReason, Message: fmt.Sprintf("source interface %q not found", key), @@ -398,7 +400,7 @@ func (r *BorderGatewayReconciler) reconcileSourceInterface(ctx context.Context, if intf.Spec.DeviceRef.Name != s.Device.Name { conditions.Set(s.BorderGateway, metav1.Condition{ - Type: v1alpha1.ReadyCondition, + Type: v1alpha1.ConfiguredCondition, Status: metav1.ConditionFalse, Reason: v1alpha1.CrossDeviceReferenceReason, Message: fmt.Sprintf("source interface %q does not belong to device %q", intf.Name, s.Device.Name), @@ -408,7 +410,7 @@ func (r *BorderGatewayReconciler) reconcileSourceInterface(ctx context.Context, if intf.Spec.Type != v1alpha1.InterfaceTypeLoopback { conditions.Set(s.BorderGateway, metav1.Condition{ - Type: v1alpha1.ReadyCondition, + Type: v1alpha1.ConfiguredCondition, Status: metav1.ConditionFalse, Reason: v1alpha1.InvalidInterfaceTypeReason, Message: fmt.Sprintf("source interface %q is not of type Loopback, got %q", intf.Name, intf.Spec.Type), @@ -427,7 +429,7 @@ func (r *BorderGatewayReconciler) reconcileInterconnectInterfaces(ctx context.Co if err := r.Get(ctx, client.ObjectKey{Name: ref.Name, Namespace: s.BorderGateway.Namespace}, intf); err != nil { if apierrors.IsNotFound(err) { conditions.Set(s.BorderGateway, metav1.Condition{ - Type: v1alpha1.ReadyCondition, + Type: v1alpha1.ConfiguredCondition, Status: metav1.ConditionFalse, Reason: v1alpha1.InterfaceNotFoundReason, Message: fmt.Sprintf("interconnect interface %q not found", ref.Name), @@ -439,7 +441,7 @@ func (r *BorderGatewayReconciler) reconcileInterconnectInterfaces(ctx context.Co if intf.Spec.DeviceRef.Name != s.Device.Name { conditions.Set(s.BorderGateway, metav1.Condition{ - Type: v1alpha1.ReadyCondition, + Type: v1alpha1.ConfiguredCondition, Status: metav1.ConditionFalse, Reason: v1alpha1.CrossDeviceReferenceReason, Message: fmt.Sprintf("interconnect interface %q does not belong to device %q", intf.Name, s.Device.Name), @@ -449,7 +451,7 @@ func (r *BorderGatewayReconciler) reconcileInterconnectInterfaces(ctx context.Co if intf.Spec.Type != v1alpha1.InterfaceTypePhysical { conditions.Set(s.BorderGateway, metav1.Condition{ - Type: v1alpha1.ReadyCondition, + Type: v1alpha1.ConfiguredCondition, Status: metav1.ConditionFalse, Reason: v1alpha1.InvalidInterfaceTypeReason, Message: fmt.Sprintf("interconnect interface %q is not of type Physical, got %q", intf.Name, intf.Spec.Type), @@ -474,7 +476,7 @@ func (r *BorderGatewayReconciler) reconcileBGPPeers(ctx context.Context, s *bord if err := r.Get(ctx, client.ObjectKey{Name: ref.Name, Namespace: s.BorderGateway.Namespace}, peer); err != nil { if apierrors.IsNotFound(err) { conditions.Set(s.BorderGateway, metav1.Condition{ - Type: v1alpha1.ReadyCondition, + Type: v1alpha1.ConfiguredCondition, Status: metav1.ConditionFalse, Reason: v1alpha1.BGPPeerNotFoundReason, Message: fmt.Sprintf("BGP peer %q not found", ref.Name), @@ -486,7 +488,7 @@ func (r *BorderGatewayReconciler) reconcileBGPPeers(ctx context.Context, s *bord if peer.Spec.DeviceRef.Name != s.Device.Name { conditions.Set(s.BorderGateway, metav1.Condition{ - Type: v1alpha1.ReadyCondition, + Type: v1alpha1.ConfiguredCondition, Status: metav1.ConditionFalse, Reason: v1alpha1.CrossDeviceReferenceReason, Message: fmt.Sprintf("BGP peer %q does not belong to device %q", peer.Name, s.Device.Name), diff --git a/internal/controller/cisco/nx/bordergateway_controller_test.go b/internal/controller/cisco/nx/bordergateway_controller_test.go index d6a8ec9c5..f6f3c157f 100644 --- a/internal/controller/cisco/nx/bordergateway_controller_test.go +++ b/internal/controller/cisco/nx/bordergateway_controller_test.go @@ -122,11 +122,13 @@ var _ = Describe("BorderGateway Controller", func() { Eventually(func(g Gomega) { resource := &nxv1alpha1.BorderGateway{} g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) - g.Expect(resource.Status.Conditions).To(HaveLen(2)) + g.Expect(resource.Status.Conditions).To(HaveLen(3)) g.Expect(resource.Status.Conditions[0].Type).To(Equal(v1alpha1.ReadyCondition)) g.Expect(resource.Status.Conditions[0].Status).To(Equal(metav1.ConditionTrue)) - g.Expect(resource.Status.Conditions[1].Type).To(Equal(v1alpha1.PausedCondition)) - g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionFalse)) + g.Expect(resource.Status.Conditions[1].Type).To(Equal(v1alpha1.ConfiguredCondition)) + g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionTrue)) + g.Expect(resource.Status.Conditions[2].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[2].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) By("Ensuring the resource is created in the provider") diff --git a/internal/controller/cisco/nx/system_controller.go b/internal/controller/cisco/nx/system_controller.go index 58551fea2..6b4735027 100644 --- a/internal/controller/cisco/nx/system_controller.go +++ b/internal/controller/cisco/nx/system_controller.go @@ -162,7 +162,7 @@ func (r *SystemReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ c } orig := obj.DeepCopy() - if conditions.InitializeConditions(obj, v1alpha1.ReadyCondition) { + if conditions.InitializeConditions(obj, v1alpha1.ReadyCondition, v1alpha1.ConfiguredCondition) { log.V(1).Info("Initializing status conditions") return ctrl.Result{}, r.Status().Update(ctx, obj) } @@ -247,6 +247,10 @@ func (r *SystemReconciler) reconcile(ctx context.Context, s *systemScope) (reter s.System.Labels[v1alpha1.DeviceLabel] = s.Device.Name + defer func() { + conditions.RecomputeReady(s.System) + }() + // Ensure the System is owned by the Device. if !controllerutil.HasControllerReference(s.System) { if err := controllerutil.SetOwnerReference(s.Device, s.System, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { @@ -267,8 +271,6 @@ func (r *SystemReconciler) reconcile(ctx context.Context, s *systemScope) (reter err := s.Provider.EnsureSystemSettings(ctx, s.System) cond := conditions.FromError(err) - // As this resource is configuration only, we use the Configured condition as top-level Ready condition. - cond.Type = v1alpha1.ReadyCondition conditions.Set(s.System, cond) return err diff --git a/internal/controller/cisco/nx/system_controller_test.go b/internal/controller/cisco/nx/system_controller_test.go index ff04a99a6..1fd12cae4 100644 --- a/internal/controller/cisco/nx/system_controller_test.go +++ b/internal/controller/cisco/nx/system_controller_test.go @@ -101,11 +101,13 @@ var _ = Describe("System Controller", func() { Eventually(func(g Gomega) { resource := &nxv1alpha1.System{} g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) - g.Expect(resource.Status.Conditions).To(HaveLen(2)) + g.Expect(resource.Status.Conditions).To(HaveLen(3)) g.Expect(resource.Status.Conditions[0].Type).To(Equal(v1alpha1.ReadyCondition)) g.Expect(resource.Status.Conditions[0].Status).To(Equal(metav1.ConditionTrue)) - g.Expect(resource.Status.Conditions[1].Type).To(Equal(v1alpha1.PausedCondition)) - g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionFalse)) + g.Expect(resource.Status.Conditions[1].Type).To(Equal(v1alpha1.ConfiguredCondition)) + g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionTrue)) + g.Expect(resource.Status.Conditions[2].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[2].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) By("Ensuring the resource is created in the provider") diff --git a/internal/controller/core/aaa_controller.go b/internal/controller/core/aaa_controller.go index c38b1061e..fdb866512 100644 --- a/internal/controller/core/aaa_controller.go +++ b/internal/controller/core/aaa_controller.go @@ -170,7 +170,7 @@ func (r *AAAReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl } orig := obj.DeepCopy() - if conditions.InitializeConditions(obj, v1alpha1.ReadyCondition) { + if conditions.InitializeConditions(obj, v1alpha1.ReadyCondition, v1alpha1.ConfiguredCondition) { log.V(1).Info("Initializing status conditions") return ctrl.Result{}, r.Status().Update(ctx, obj) } @@ -242,6 +242,10 @@ func (r *AAAReconciler) reconcile(ctx context.Context, s *aaaScope) (reterr erro s.AAA.Labels[v1alpha1.DeviceLabel] = s.Device.Name + defer func() { + conditions.RecomputeReady(s.AAA) + }() + // Ensure the AAA is owned by the Device. if !controllerutil.HasControllerReference(s.AAA) { if err := controllerutil.SetOwnerReference(s.Device, s.AAA, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { @@ -290,8 +294,6 @@ func (r *AAAReconciler) reconcile(ctx context.Context, s *aaaScope) (reterr erro }) cond := conditions.FromError(err) - // As this resource is configuration only, we use the Configured condition as top-level Ready condition. - cond.Type = v1alpha1.ReadyCondition conditions.Set(s.AAA, cond) return err diff --git a/internal/controller/core/acl_controller.go b/internal/controller/core/acl_controller.go index 77510b9bd..b3a39c971 100644 --- a/internal/controller/core/acl_controller.go +++ b/internal/controller/core/acl_controller.go @@ -172,7 +172,7 @@ func (r *AccessControlListReconciler) Reconcile(ctx context.Context, req ctrl.Re } orig := obj.DeepCopy() - if conditions.InitializeConditions(obj, v1alpha1.ReadyCondition) { + if conditions.InitializeConditions(obj, v1alpha1.ReadyCondition, v1alpha1.ConfiguredCondition) { log.V(1).Info("Initializing status conditions") return ctrl.Result{}, r.Status().Update(ctx, obj) } @@ -271,6 +271,10 @@ func (r *AccessControlListReconciler) reconcile(ctx context.Context, s *aclScope s.ACL.Labels[v1alpha1.DeviceLabel] = s.Device.Name + defer func() { + conditions.RecomputeReady(s.ACL) + }() + // Ensure the AccessControlList is owned by the Device. if !controllerutil.HasControllerReference(s.ACL) { if err := controllerutil.SetOwnerReference(s.Device, s.ACL, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { @@ -300,8 +304,6 @@ func (r *AccessControlListReconciler) reconcile(ctx context.Context, s *aclScope }) cond := conditions.FromError(err) - // As this resource is configuration only, we use the Configured condition as top-level Ready condition. - cond.Type = v1alpha1.ReadyCondition conditions.Set(s.ACL, cond) return err diff --git a/internal/controller/core/acl_controller_test.go b/internal/controller/core/acl_controller_test.go index 45718d5af..476cf0eed 100644 --- a/internal/controller/core/acl_controller_test.go +++ b/internal/controller/core/acl_controller_test.go @@ -118,11 +118,13 @@ var _ = Describe("AccessControlList Controller", func() { Eventually(func(g Gomega) { resource := &v1alpha1.AccessControlList{} g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) - g.Expect(resource.Status.Conditions).To(HaveLen(2)) + g.Expect(resource.Status.Conditions).To(HaveLen(3)) g.Expect(resource.Status.Conditions[0].Type).To(Equal(v1alpha1.ReadyCondition)) g.Expect(resource.Status.Conditions[0].Status).To(Equal(metav1.ConditionTrue)) - g.Expect(resource.Status.Conditions[1].Type).To(Equal(v1alpha1.PausedCondition)) - g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionFalse)) + g.Expect(resource.Status.Conditions[1].Type).To(Equal(v1alpha1.ConfiguredCondition)) + g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionTrue)) + g.Expect(resource.Status.Conditions[2].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[2].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) By("Ensuring the resource is created in the provider") diff --git a/internal/controller/core/banner_controller.go b/internal/controller/core/banner_controller.go index 9e474648e..9aaf4f770 100644 --- a/internal/controller/core/banner_controller.go +++ b/internal/controller/core/banner_controller.go @@ -176,7 +176,7 @@ func (r *BannerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ c } orig := obj.DeepCopy() - if conditions.InitializeConditions(obj, v1alpha1.ReadyCondition) { + if conditions.InitializeConditions(obj, v1alpha1.ReadyCondition, v1alpha1.ConfiguredCondition) { log.V(1).Info("Initializing status conditions") return ctrl.Result{}, r.Status().Update(ctx, obj) } @@ -287,6 +287,10 @@ func (r *BannerReconciler) reconcile(ctx context.Context, s *bannerScope) (reter s.Banner.Labels[v1alpha1.DeviceLabel] = s.Device.Name + defer func() { + conditions.RecomputeReady(s.Banner) + }() + // Ensure the Banner is owned by the Device. if !controllerutil.HasControllerReference(s.Banner) { if err := controllerutil.SetOwnerReference(s.Device, s.Banner, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { @@ -316,8 +320,6 @@ func (r *BannerReconciler) reconcile(ctx context.Context, s *bannerScope) (reter }) cond := conditions.FromError(err) - // As this resource is configuration only, we use the Configured condition as top-level Ready condition. - cond.Type = v1alpha1.ReadyCondition conditions.Set(s.Banner, cond) return err diff --git a/internal/controller/core/banner_controller_test.go b/internal/controller/core/banner_controller_test.go index 411ccf2d4..4527a5e11 100644 --- a/internal/controller/core/banner_controller_test.go +++ b/internal/controller/core/banner_controller_test.go @@ -102,11 +102,13 @@ var _ = Describe("Banner Controller", func() { Eventually(func(g Gomega) { resource := &v1alpha1.Banner{} g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) - g.Expect(resource.Status.Conditions).To(HaveLen(2)) + g.Expect(resource.Status.Conditions).To(HaveLen(3)) g.Expect(resource.Status.Conditions[0].Type).To(Equal(v1alpha1.ReadyCondition)) g.Expect(resource.Status.Conditions[0].Status).To(Equal(metav1.ConditionTrue)) - g.Expect(resource.Status.Conditions[1].Type).To(Equal(v1alpha1.PausedCondition)) - g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionFalse)) + g.Expect(resource.Status.Conditions[1].Type).To(Equal(v1alpha1.ConfiguredCondition)) + g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionTrue)) + g.Expect(resource.Status.Conditions[2].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[2].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) By("Ensuring the resource is created in the provider") @@ -163,11 +165,13 @@ var _ = Describe("Banner Controller", func() { Eventually(func(g Gomega) { resource := &v1alpha1.Banner{} g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) - g.Expect(resource.Status.Conditions).To(HaveLen(2)) + g.Expect(resource.Status.Conditions).To(HaveLen(3)) g.Expect(resource.Status.Conditions[0].Type).To(Equal(v1alpha1.ReadyCondition)) g.Expect(resource.Status.Conditions[0].Status).To(Equal(metav1.ConditionTrue)) - g.Expect(resource.Status.Conditions[1].Type).To(Equal(v1alpha1.PausedCondition)) - g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionFalse)) + g.Expect(resource.Status.Conditions[1].Type).To(Equal(v1alpha1.ConfiguredCondition)) + g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionTrue)) + g.Expect(resource.Status.Conditions[2].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[2].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) By("Ensuring the resource is created in the provider") diff --git a/internal/controller/core/bgp_controller.go b/internal/controller/core/bgp_controller.go index 58e1f6892..7e9a9893e 100644 --- a/internal/controller/core/bgp_controller.go +++ b/internal/controller/core/bgp_controller.go @@ -185,7 +185,7 @@ func (r *BGPReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl } orig := obj.DeepCopy() - if conditions.InitializeConditions(obj, v1alpha1.ReadyCondition) { + if conditions.InitializeConditions(obj, v1alpha1.ReadyCondition, v1alpha1.ConfiguredCondition) { log.V(1).Info("Initializing status conditions") return ctrl.Result{}, r.Status().Update(ctx, obj) } @@ -308,7 +308,7 @@ func (r *BGPReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager) UpdateFunc: func(e event.UpdateEvent) bool { oldVRF := e.ObjectOld.(*v1alpha1.VRF) newVRF := e.ObjectNew.(*v1alpha1.VRF) - return conditions.IsReady(oldVRF) != conditions.IsReady(newVRF) + return conditions.IsConfigured(oldVRF) != conditions.IsConfigured(newVRF) }, GenericFunc: func(e event.GenericEvent) bool { return false @@ -348,6 +348,10 @@ func (r *BGPReconciler) reconcile(ctx context.Context, s *bgpScope) (reterr erro s.BGP.Labels[v1alpha1.DeviceLabel] = s.Device.Name + defer func() { + conditions.RecomputeReady(s.BGP) + }() + // Ensure the BGP is owned by the Device. if !controllerutil.HasControllerReference(s.BGP) { if err := controllerutil.SetOwnerReference(s.Device, s.BGP, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { @@ -390,8 +394,6 @@ func (r *BGPReconciler) reconcile(ctx context.Context, s *bgpScope) (reterr erro }) cond := conditions.FromError(err) - // As this resource is configuration only, we use the Configured condition as top-level Ready condition. - cond.Type = v1alpha1.ReadyCondition conditions.Set(s.BGP, cond) return err @@ -443,7 +445,7 @@ func (r *BGPReconciler) reconcileVRF(ctx context.Context, bgp *v1alpha1.BGP, dev }, vrf); err != nil { if apierrors.IsNotFound(err) { conditions.Set(bgp, metav1.Condition{ - Type: v1alpha1.ReadyCondition, + Type: v1alpha1.ConfiguredCondition, Status: metav1.ConditionFalse, Reason: v1alpha1.VRFNotFoundReason, Message: fmt.Sprintf("VRF %s not found", bgp.Spec.VrfRef.Name), @@ -454,7 +456,7 @@ func (r *BGPReconciler) reconcileVRF(ctx context.Context, bgp *v1alpha1.BGP, dev } if vrf.Spec.DeviceRef.Name != device.Name { conditions.Set(bgp, metav1.Condition{ - Type: v1alpha1.ReadyCondition, + Type: v1alpha1.ConfiguredCondition, Status: metav1.ConditionFalse, Reason: v1alpha1.CrossDeviceReferenceReason, Message: fmt.Sprintf("VRF %s belongs to device %s, not %s", bgp.Spec.VrfRef.Name, vrf.Spec.DeviceRef.Name, device.Name), @@ -462,10 +464,10 @@ func (r *BGPReconciler) reconcileVRF(ctx context.Context, bgp *v1alpha1.BGP, dev return nil, reconcile.TerminalError(fmt.Errorf("vrf %s belongs to different device", bgp.Spec.VrfRef.Name)) } - if !conditions.IsReady(vrf) { + if !conditions.IsConfigured(vrf) { // VRF uses ReadyCondition as its top-level configured state (no separate ConfiguredCondition). conditions.Set(bgp, metav1.Condition{ - Type: v1alpha1.ReadyCondition, + Type: v1alpha1.ConfiguredCondition, Status: metav1.ConditionFalse, Reason: v1alpha1.WaitingForDependenciesReason, Message: fmt.Sprintf("Waiting for VRF %s to become ready", bgp.Spec.VrfRef.Name), @@ -497,7 +499,7 @@ func (r *BGPReconciler) reconcileRedistributeDirectPolicies(ctx context.Context, if err := r.Get(ctx, types.NamespacedName{Name: ref.Name, Namespace: bgp.Namespace}, rp); err != nil { if apierrors.IsNotFound(err) { conditions.Set(bgp, metav1.Condition{ - Type: v1alpha1.ReadyCondition, + Type: v1alpha1.ConfiguredCondition, Status: metav1.ConditionFalse, Reason: v1alpha1.WaitingForDependenciesReason, Message: fmt.Sprintf("RoutingPolicy %s not found", ref.Name), @@ -509,7 +511,7 @@ func (r *BGPReconciler) reconcileRedistributeDirectPolicies(ctx context.Context, if rp.Spec.DeviceRef.Name != device.Name { conditions.Set(bgp, metav1.Condition{ - Type: v1alpha1.ReadyCondition, + Type: v1alpha1.ConfiguredCondition, Status: metav1.ConditionFalse, Reason: v1alpha1.CrossDeviceReferenceReason, Message: fmt.Sprintf("RoutingPolicy %s belongs to device %s, not %s", ref.Name, rp.Spec.DeviceRef.Name, device.Name), diff --git a/internal/controller/core/bgp_controller_test.go b/internal/controller/core/bgp_controller_test.go index 2a70a6c2e..6a9bb6342 100644 --- a/internal/controller/core/bgp_controller_test.go +++ b/internal/controller/core/bgp_controller_test.go @@ -115,11 +115,13 @@ var _ = Describe("BGP Controller", func() { Eventually(func(g Gomega) { resource := &v1alpha1.BGP{} g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(bgp), resource)).To(Succeed()) - g.Expect(resource.Status.Conditions).To(HaveLen(2)) + g.Expect(resource.Status.Conditions).To(HaveLen(3)) g.Expect(resource.Status.Conditions[0].Type).To(Equal(v1alpha1.ReadyCondition)) g.Expect(resource.Status.Conditions[0].Status).To(Equal(metav1.ConditionTrue)) - g.Expect(resource.Status.Conditions[1].Type).To(Equal(v1alpha1.PausedCondition)) - g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionFalse)) + g.Expect(resource.Status.Conditions[1].Type).To(Equal(v1alpha1.ConfiguredCondition)) + g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionTrue)) + g.Expect(resource.Status.Conditions[2].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[2].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) By("Ensuring the resource is created in the provider") @@ -144,11 +146,11 @@ var _ = Describe("BGP Controller", func() { } Expect(k8sClient.Create(ctx, bgp)).To(Succeed()) - By("Expecting ReadyCondition to be False with VRFNotFoundReason reason") + By("Expecting ConfiguredCondition to be False with VRFNotFoundReason reason") Eventually(func(g Gomega) { resource := &v1alpha1.BGP{} g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(bgp), resource)).To(Succeed()) - cond := conditions.Get(resource, v1alpha1.ReadyCondition) + cond := conditions.Get(resource, v1alpha1.ConfiguredCondition) g.Expect(cond).ToNot(BeNil()) g.Expect(cond.Status).To(Equal(metav1.ConditionFalse)) g.Expect(cond.Reason).To(Equal(v1alpha1.VRFNotFoundReason)) @@ -223,11 +225,11 @@ var _ = Describe("BGP Controller", func() { } Expect(k8sClient.Create(ctx, bgp)).To(Succeed()) - By("Expecting ReadyCondition to be False with WaitingForDependencies reason") + By("Expecting ConfiguredCondition to be False with WaitingForDependencies reason") Eventually(func(g Gomega) { resource := &v1alpha1.BGP{} g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(bgp), resource)).To(Succeed()) - cond := conditions.Get(resource, v1alpha1.ReadyCondition) + cond := conditions.Get(resource, v1alpha1.ConfiguredCondition) g.Expect(cond).ToNot(BeNil()) g.Expect(cond.Status).To(Equal(metav1.ConditionFalse)) g.Expect(cond.Reason).To(Equal(v1alpha1.WaitingForDependenciesReason)) diff --git a/internal/controller/core/bgp_peer_controller.go b/internal/controller/core/bgp_peer_controller.go index 72ec7ee4d..201ca4fb4 100644 --- a/internal/controller/core/bgp_peer_controller.go +++ b/internal/controller/core/bgp_peer_controller.go @@ -315,7 +315,7 @@ func (r *BGPPeerReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manag UpdateFunc: func(e event.UpdateEvent) bool { oldBGP := e.ObjectOld.(*v1alpha1.BGP) newBGP := e.ObjectNew.(*v1alpha1.BGP) - return conditions.IsReady(oldBGP) != conditions.IsReady(newBGP) + return conditions.IsConfigured(oldBGP) != conditions.IsConfigured(newBGP) }, GenericFunc: func(e event.GenericEvent) bool { return false @@ -331,7 +331,7 @@ func (r *BGPPeerReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manag UpdateFunc: func(e event.UpdateEvent) bool { oldVRF := e.ObjectOld.(*v1alpha1.VRF) newVRF := e.ObjectNew.(*v1alpha1.VRF) - return conditions.IsReady(oldVRF) != conditions.IsReady(newVRF) + return conditions.IsConfigured(oldVRF) != conditions.IsConfigured(newVRF) }, GenericFunc: func(e event.GenericEvent) bool { return false @@ -389,7 +389,7 @@ func (r *BGPPeerReconciler) reconcile(ctx context.Context, s *bgpPeerScope) (ret // BGP has no operational condition, so its ready condition reflects only successful configuration. // Wait for the BGP watch to re-trigger rather than requeuing periodically. - if !conditions.IsReady(bgp) { + if !conditions.IsConfigured(bgp) { conditions.Set(s.BGPPeer, metav1.Condition{ Type: v1alpha1.ConfiguredCondition, Status: metav1.ConditionFalse, @@ -639,7 +639,7 @@ func (r *BGPPeerReconciler) reconcileVRF(ctx context.Context, peer *v1alpha1.BGP return nil, reconcile.TerminalError(fmt.Errorf("vrf %s belongs to different device", bgp.Spec.VrfRef.Name)) } - if !conditions.IsReady(vrf) { + if !conditions.IsConfigured(vrf) { // VRF uses ReadyCondition as its top-level configured state (no separate ConfiguredCondition). conditions.Set(peer, metav1.Condition{ Type: v1alpha1.ConfiguredCondition, diff --git a/internal/controller/core/certificate_controller.go b/internal/controller/core/certificate_controller.go index 640a8c75d..ff88d4257 100644 --- a/internal/controller/core/certificate_controller.go +++ b/internal/controller/core/certificate_controller.go @@ -175,7 +175,7 @@ func (r *CertificateReconciler) Reconcile(ctx context.Context, req ctrl.Request) } orig := obj.DeepCopy() - if conditions.InitializeConditions(obj, v1alpha1.ReadyCondition) { + if conditions.InitializeConditions(obj, v1alpha1.ReadyCondition, v1alpha1.ConfiguredCondition) { log.V(1).Info("Initializing status conditions") return ctrl.Result{}, r.Status().Update(ctx, obj) } @@ -280,6 +280,10 @@ func (r *CertificateReconciler) reconcile(ctx context.Context, s *certificateSco s.Certificate.Labels[v1alpha1.DeviceLabel] = s.Device.Name + defer func() { + conditions.RecomputeReady(s.Certificate) + }() + // Ensure the Certificate is owned by the Device. if !controllerutil.HasControllerReference(s.Certificate) { if err := controllerutil.SetOwnerReference(s.Device, s.Certificate, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { @@ -309,8 +313,6 @@ func (r *CertificateReconciler) reconcile(ctx context.Context, s *certificateSco }) cond := conditions.FromError(err) - // As this resource is configuration only, we use the Configured condition as top-level Ready condition. - cond.Type = v1alpha1.ReadyCondition conditions.Set(s.Certificate, cond) return err diff --git a/internal/controller/core/certificate_controller_test.go b/internal/controller/core/certificate_controller_test.go index 501f8f0e5..ad390adbd 100644 --- a/internal/controller/core/certificate_controller_test.go +++ b/internal/controller/core/certificate_controller_test.go @@ -133,11 +133,13 @@ var _ = Describe("Certificate Controller", func() { Eventually(func(g Gomega) { resource := &v1alpha1.Certificate{} g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) - g.Expect(resource.Status.Conditions).To(HaveLen(2)) + g.Expect(resource.Status.Conditions).To(HaveLen(3)) g.Expect(resource.Status.Conditions[0].Type).To(Equal(v1alpha1.ReadyCondition)) g.Expect(resource.Status.Conditions[0].Status).To(Equal(metav1.ConditionTrue)) - g.Expect(resource.Status.Conditions[1].Type).To(Equal(v1alpha1.PausedCondition)) - g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionFalse)) + g.Expect(resource.Status.Conditions[1].Type).To(Equal(v1alpha1.ConfiguredCondition)) + g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionTrue)) + g.Expect(resource.Status.Conditions[2].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[2].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) By("Ensuring the resource is created in the provider") diff --git a/internal/controller/core/dhcprelay_controller.go b/internal/controller/core/dhcprelay_controller.go index ec88c2a47..7f6e7488c 100644 --- a/internal/controller/core/dhcprelay_controller.go +++ b/internal/controller/core/dhcprelay_controller.go @@ -178,7 +178,7 @@ func (r *DHCPRelayReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( } orig := obj.DeepCopy() - if conditions.InitializeConditions(obj, v1alpha1.ReadyCondition) { + if conditions.InitializeConditions(obj, v1alpha1.ReadyCondition, v1alpha1.ConfiguredCondition) { log.V(1).Info("Initializing status conditions") return ctrl.Result{}, r.Status().Update(ctx, obj) } @@ -223,6 +223,10 @@ func (r *DHCPRelayReconciler) reconcile(ctx context.Context, s *dhcprelayScope) } s.DHCPRelay.Labels[v1alpha1.DeviceLabel] = s.Device.Name + defer func() { + conditions.RecomputeReady(s.DHCPRelay) + }() + // Ensure the DHCPRelay is owned by the Device. if !controllerutil.HasControllerReference(s.DHCPRelay) { if err := controllerutil.SetOwnerReference(s.Device, s.DHCPRelay, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { @@ -270,8 +274,6 @@ func (r *DHCPRelayReconciler) reconcile(ctx context.Context, s *dhcprelayScope) }) cond := conditions.FromError(err) - // As this resource is configuration only, we use the Configured condition as top-level Ready condition. - cond.Type = v1alpha1.ReadyCondition conditions.Set(s.DHCPRelay, cond) if err != nil { @@ -536,7 +538,7 @@ func (r *DHCPRelayReconciler) reconcileVRFRef(ctx context.Context, s *dhcprelayS } // Verify the VRF is ready (configured) on the device - if !conditions.IsReady(vrf) { + if !conditions.IsConfigured(vrf) { conditions.Set(s.DHCPRelay, metav1.Condition{ Type: v1alpha1.ConfiguredCondition, Status: metav1.ConditionFalse, diff --git a/internal/controller/core/dhcprelay_controller_test.go b/internal/controller/core/dhcprelay_controller_test.go index e11406d5c..738357138 100644 --- a/internal/controller/core/dhcprelay_controller_test.go +++ b/internal/controller/core/dhcprelay_controller_test.go @@ -191,9 +191,13 @@ var _ = Describe("DHCPRelay Controller", func() { dhcprelay = &v1alpha1.DHCPRelay{} g.Expect(k8sClient.Get(ctx, resourceKey, dhcprelay)).To(Succeed()) - cond := meta.FindStatusCondition(dhcprelay.Status.Conditions, v1alpha1.ReadyCondition) - g.Expect(cond).ToNot(BeNil()) - g.Expect(cond.Status).To(Equal(metav1.ConditionTrue)) + g.Expect(dhcprelay.Status.Conditions).To(HaveLen(3)) + g.Expect(dhcprelay.Status.Conditions[0].Type).To(Equal(v1alpha1.ReadyCondition)) + g.Expect(dhcprelay.Status.Conditions[0].Status).To(Equal(metav1.ConditionTrue)) + g.Expect(dhcprelay.Status.Conditions[1].Type).To(Equal(v1alpha1.ConfiguredCondition)) + g.Expect(dhcprelay.Status.Conditions[1].Status).To(Equal(metav1.ConditionTrue)) + g.Expect(dhcprelay.Status.Conditions[2].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(dhcprelay.Status.Conditions[2].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) By("Verifying the status contains configured interface refs") diff --git a/internal/controller/core/dns_controller.go b/internal/controller/core/dns_controller.go index 6abe047ad..f93c8e782 100644 --- a/internal/controller/core/dns_controller.go +++ b/internal/controller/core/dns_controller.go @@ -172,7 +172,7 @@ func (r *DNSReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl } orig := obj.DeepCopy() - if conditions.InitializeConditions(obj, v1alpha1.ReadyCondition) { + if conditions.InitializeConditions(obj, v1alpha1.ReadyCondition, v1alpha1.ConfiguredCondition) { log.V(1).Info("Initializing status conditions") return ctrl.Result{}, r.Status().Update(ctx, obj) } @@ -271,6 +271,10 @@ func (r *DNSReconciler) reconcile(ctx context.Context, s *dnsScope) (reterr erro s.DNS.Labels[v1alpha1.DeviceLabel] = s.Device.Name + defer func() { + conditions.RecomputeReady(s.DNS) + }() + // Ensure the DNS is owned by the Device. if !controllerutil.HasControllerReference(s.DNS) { if err := controllerutil.SetOwnerReference(s.Device, s.DNS, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { @@ -294,8 +298,6 @@ func (r *DNSReconciler) reconcile(ctx context.Context, s *dnsScope) (reterr erro }) cond := conditions.FromError(err) - // As this resource is configuration only, we use the Configured condition as top-level Ready condition. - cond.Type = v1alpha1.ReadyCondition conditions.Set(s.DNS, cond) return err diff --git a/internal/controller/core/dns_controller_test.go b/internal/controller/core/dns_controller_test.go index 4b5c86c94..e7e833da1 100644 --- a/internal/controller/core/dns_controller_test.go +++ b/internal/controller/core/dns_controller_test.go @@ -104,11 +104,13 @@ var _ = Describe("DNS Controller", func() { Eventually(func(g Gomega) { resource := &v1alpha1.DNS{} g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) - g.Expect(resource.Status.Conditions).To(HaveLen(2)) + g.Expect(resource.Status.Conditions).To(HaveLen(3)) g.Expect(resource.Status.Conditions[0].Type).To(Equal(v1alpha1.ReadyCondition)) g.Expect(resource.Status.Conditions[0].Status).To(Equal(metav1.ConditionTrue)) - g.Expect(resource.Status.Conditions[1].Type).To(Equal(v1alpha1.PausedCondition)) - g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionFalse)) + g.Expect(resource.Status.Conditions[1].Type).To(Equal(v1alpha1.ConfiguredCondition)) + g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionTrue)) + g.Expect(resource.Status.Conditions[2].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[2].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) By("Ensuring the resource is created in the provider") diff --git a/internal/controller/core/evpninstance_controller.go b/internal/controller/core/evpninstance_controller.go index 724c293b4..7e79c22b3 100644 --- a/internal/controller/core/evpninstance_controller.go +++ b/internal/controller/core/evpninstance_controller.go @@ -174,7 +174,7 @@ func (r *EVPNInstanceReconciler) Reconcile(ctx context.Context, req ctrl.Request } orig := obj.DeepCopy() - if conditions.InitializeConditions(obj, v1alpha1.ReadyCondition) { + if conditions.InitializeConditions(obj, v1alpha1.ReadyCondition, v1alpha1.ConfiguredCondition) { log.V(1).Info("Initializing status conditions") return ctrl.Result{}, r.Status().Update(ctx, obj) } @@ -326,6 +326,10 @@ func (r *EVPNInstanceReconciler) reconcile(ctx context.Context, s *eviScope) (re s.EVPNInstance.Labels[v1alpha1.DeviceLabel] = s.Device.Name + defer func() { + conditions.RecomputeReady(s.EVPNInstance) + }() + // Ensure the EVPNInstance is owned by the Device. if !controllerutil.HasControllerReference(s.EVPNInstance) { if err := controllerutil.SetOwnerReference(s.Device, s.EVPNInstance, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { @@ -369,8 +373,6 @@ func (r *EVPNInstanceReconciler) reconcile(ctx context.Context, s *eviScope) (re }) cond := conditions.FromError(err) - // As this resource is configuration only, we use the Configured condition as top-level Ready condition. - cond.Type = v1alpha1.ReadyCondition conditions.Set(s.EVPNInstance, cond) return err @@ -388,7 +390,7 @@ func (r *EVPNInstanceReconciler) reconcileVLAN(ctx context.Context, s *eviScope) if err := r.Get(ctx, key, vlan); err != nil { if apierrors.IsNotFound(err) { conditions.Set(s.EVPNInstance, metav1.Condition{ - Type: v1alpha1.ReadyCondition, + Type: v1alpha1.ConfiguredCondition, Status: metav1.ConditionFalse, Reason: v1alpha1.VLANNotFoundReason, Message: fmt.Sprintf("referenced VLAN %q not found", key), @@ -400,7 +402,7 @@ func (r *EVPNInstanceReconciler) reconcileVLAN(ctx context.Context, s *eviScope) if vlan.Spec.DeviceRef.Name != s.Device.Name { conditions.Set(s.EVPNInstance, metav1.Condition{ - Type: v1alpha1.ReadyCondition, + Type: v1alpha1.ConfiguredCondition, Status: metav1.ConditionFalse, Reason: v1alpha1.CrossDeviceReferenceReason, Message: fmt.Sprintf("referenced VLAN %q does not belong to device %q", vlan.Name, s.Device.Name), @@ -410,7 +412,7 @@ func (r *EVPNInstanceReconciler) reconcileVLAN(ctx context.Context, s *eviScope) if vlan.Status.BridgedBy != nil && vlan.Status.BridgedBy.Name != s.EVPNInstance.Name { conditions.Set(s.EVPNInstance, metav1.Condition{ - Type: v1alpha1.ReadyCondition, + Type: v1alpha1.ConfiguredCondition, Status: metav1.ConditionFalse, Reason: v1alpha1.VLANAlreadyInUseReason, Message: fmt.Sprintf("VLAN %q is already in use by EVPNInstance %q", vlan.Name, vlan.Status.BridgedBy.Name), diff --git a/internal/controller/core/evpninstance_controller_test.go b/internal/controller/core/evpninstance_controller_test.go index 1165bc63d..1fce7e4e8 100644 --- a/internal/controller/core/evpninstance_controller_test.go +++ b/internal/controller/core/evpninstance_controller_test.go @@ -130,11 +130,13 @@ var _ = Describe("EVPNInstance Controller", func() { Eventually(func(g Gomega) { resource := &v1alpha1.EVPNInstance{} g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) - g.Expect(resource.Status.Conditions).To(HaveLen(2)) + g.Expect(resource.Status.Conditions).To(HaveLen(3)) g.Expect(resource.Status.Conditions[0].Type).To(Equal(v1alpha1.ReadyCondition)) g.Expect(resource.Status.Conditions[0].Status).To(Equal(metav1.ConditionTrue)) - g.Expect(resource.Status.Conditions[1].Type).To(Equal(v1alpha1.PausedCondition)) - g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionFalse)) + g.Expect(resource.Status.Conditions[1].Type).To(Equal(v1alpha1.ConfiguredCondition)) + g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionTrue)) + g.Expect(resource.Status.Conditions[2].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[2].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) By("Verifying the VLAN is labeled with L2VNI label") @@ -186,12 +188,14 @@ var _ = Describe("EVPNInstance Controller", func() { Eventually(func(g Gomega) { resource := &v1alpha1.EVPNInstance{} g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) - g.Expect(resource.Status.Conditions).To(HaveLen(2)) + g.Expect(resource.Status.Conditions).To(HaveLen(3)) g.Expect(resource.Status.Conditions[0].Type).To(Equal(v1alpha1.ReadyCondition)) g.Expect(resource.Status.Conditions[0].Status).To(Equal(metav1.ConditionFalse)) - g.Expect(resource.Status.Conditions[0].Reason).To(Equal(v1alpha1.VLANNotFoundReason)) - g.Expect(resource.Status.Conditions[1].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[1].Type).To(Equal(v1alpha1.ConfiguredCondition)) g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionFalse)) + g.Expect(resource.Status.Conditions[1].Reason).To(Equal(v1alpha1.VLANNotFoundReason)) + g.Expect(resource.Status.Conditions[2].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[2].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) }) @@ -238,12 +242,14 @@ var _ = Describe("EVPNInstance Controller", func() { Eventually(func(g Gomega) { resource := &v1alpha1.EVPNInstance{} g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) - g.Expect(resource.Status.Conditions).To(HaveLen(2)) + g.Expect(resource.Status.Conditions).To(HaveLen(3)) g.Expect(resource.Status.Conditions[0].Type).To(Equal(v1alpha1.ReadyCondition)) g.Expect(resource.Status.Conditions[0].Status).To(Equal(metav1.ConditionFalse)) - g.Expect(resource.Status.Conditions[0].Reason).To(Equal(v1alpha1.CrossDeviceReferenceReason)) - g.Expect(resource.Status.Conditions[1].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[1].Type).To(Equal(v1alpha1.ConfiguredCondition)) g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionFalse)) + g.Expect(resource.Status.Conditions[1].Reason).To(Equal(v1alpha1.CrossDeviceReferenceReason)) + g.Expect(resource.Status.Conditions[2].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[2].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) }) }) diff --git a/internal/controller/core/isis_controller.go b/internal/controller/core/isis_controller.go index 00c77033b..d88a3b56f 100644 --- a/internal/controller/core/isis_controller.go +++ b/internal/controller/core/isis_controller.go @@ -173,7 +173,7 @@ func (r *ISISReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctr } orig := obj.DeepCopy() - if conditions.InitializeConditions(obj, v1alpha1.ReadyCondition) { + if conditions.InitializeConditions(obj, v1alpha1.ReadyCondition, v1alpha1.ConfiguredCondition) { log.V(1).Info("Initializing status conditions") return ctrl.Result{}, r.Status().Update(ctx, obj) } @@ -290,6 +290,10 @@ func (r *ISISReconciler) reconcile(ctx context.Context, s *isisScope) (reterr er s.ISIS.Labels[v1alpha1.DeviceLabel] = s.Device.Name + defer func() { + conditions.RecomputeReady(s.ISIS) + }() + // Ensure the ISIS is owned by the Device. if !controllerutil.HasControllerReference(s.ISIS) { if err := controllerutil.SetOwnerReference(s.Device, s.ISIS, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { @@ -297,10 +301,6 @@ func (r *ISISReconciler) reconcile(ctx context.Context, s *isisScope) (reterr er } } - defer func() { - conditions.RecomputeReady(s.ISIS) - }() - var interfaces []*v1alpha1.Interface for _, iface := range s.ISIS.Spec.InterfaceRefs { intf := new(v1alpha1.Interface) @@ -347,8 +347,6 @@ func (r *ISISReconciler) reconcile(ctx context.Context, s *isisScope) (reterr er }) cond := conditions.FromError(err) - // As this resource is configuration only, we use the Configured condition as top-level Ready condition. - cond.Type = v1alpha1.ReadyCondition conditions.Set(s.ISIS, cond) return err diff --git a/internal/controller/core/isis_controller_test.go b/internal/controller/core/isis_controller_test.go index f6d635ac1..c8a5cd831 100644 --- a/internal/controller/core/isis_controller_test.go +++ b/internal/controller/core/isis_controller_test.go @@ -105,11 +105,13 @@ var _ = Describe("ISIS Controller", func() { Eventually(func(g Gomega) { resource := &v1alpha1.ISIS{} g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) - g.Expect(resource.Status.Conditions).To(HaveLen(2)) + g.Expect(resource.Status.Conditions).To(HaveLen(3)) g.Expect(resource.Status.Conditions[0].Type).To(Equal(v1alpha1.ReadyCondition)) g.Expect(resource.Status.Conditions[0].Status).To(Equal(metav1.ConditionTrue)) - g.Expect(resource.Status.Conditions[1].Type).To(Equal(v1alpha1.PausedCondition)) - g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionFalse)) + g.Expect(resource.Status.Conditions[1].Type).To(Equal(v1alpha1.ConfiguredCondition)) + g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionTrue)) + g.Expect(resource.Status.Conditions[2].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[2].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) By("Ensuring the resource is created in the provider") diff --git a/internal/controller/core/managementaccess_controller.go b/internal/controller/core/managementaccess_controller.go index 1cd63f6d2..5fdcbc7c5 100644 --- a/internal/controller/core/managementaccess_controller.go +++ b/internal/controller/core/managementaccess_controller.go @@ -172,7 +172,7 @@ func (r *ManagementAccessReconciler) Reconcile(ctx context.Context, req ctrl.Req } orig := obj.DeepCopy() - if conditions.InitializeConditions(obj, v1alpha1.ReadyCondition) { + if conditions.InitializeConditions(obj, v1alpha1.ReadyCondition, v1alpha1.ConfiguredCondition) { log.V(1).Info("Initializing status conditions") return ctrl.Result{}, r.Status().Update(ctx, obj) } @@ -271,6 +271,10 @@ func (r *ManagementAccessReconciler) reconcile(ctx context.Context, s *managemen s.ManagementAccess.Labels[v1alpha1.DeviceLabel] = s.Device.Name + defer func() { + conditions.RecomputeReady(s.ManagementAccess) + }() + // Ensure the ManagementAccess is owned by the Device. if !controllerutil.HasControllerReference(s.ManagementAccess) { if err := controllerutil.SetOwnerReference(s.Device, s.ManagementAccess, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { @@ -294,8 +298,6 @@ func (r *ManagementAccessReconciler) reconcile(ctx context.Context, s *managemen }) cond := conditions.FromError(err) - // As this resource is configuration only, we use the Configured condition as top-level Ready condition. - cond.Type = v1alpha1.ReadyCondition conditions.Set(s.ManagementAccess, cond) return err diff --git a/internal/controller/core/managementaccess_controller_test.go b/internal/controller/core/managementaccess_controller_test.go index d5b10b730..478acd9e3 100644 --- a/internal/controller/core/managementaccess_controller_test.go +++ b/internal/controller/core/managementaccess_controller_test.go @@ -102,11 +102,13 @@ var _ = Describe("ManagementAccess Controller", func() { Eventually(func(g Gomega) { resource := &v1alpha1.ManagementAccess{} g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) - g.Expect(resource.Status.Conditions).To(HaveLen(2)) + g.Expect(resource.Status.Conditions).To(HaveLen(3)) g.Expect(resource.Status.Conditions[0].Type).To(Equal(v1alpha1.ReadyCondition)) g.Expect(resource.Status.Conditions[0].Status).To(Equal(metav1.ConditionTrue)) - g.Expect(resource.Status.Conditions[1].Type).To(Equal(v1alpha1.PausedCondition)) - g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionFalse)) + g.Expect(resource.Status.Conditions[1].Type).To(Equal(v1alpha1.ConfiguredCondition)) + g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionTrue)) + g.Expect(resource.Status.Conditions[2].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[2].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) By("Ensuring the resource is created in the provider") diff --git a/internal/controller/core/ntp_controller.go b/internal/controller/core/ntp_controller.go index 806799a50..6a1988b33 100644 --- a/internal/controller/core/ntp_controller.go +++ b/internal/controller/core/ntp_controller.go @@ -172,7 +172,7 @@ func (r *NTPReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl } orig := obj.DeepCopy() - if conditions.InitializeConditions(obj, v1alpha1.ReadyCondition) { + if conditions.InitializeConditions(obj, v1alpha1.ReadyCondition, v1alpha1.ConfiguredCondition) { log.V(1).Info("Initializing status conditions") return ctrl.Result{}, r.Status().Update(ctx, obj) } @@ -271,6 +271,10 @@ func (r *NTPReconciler) reconcile(ctx context.Context, s *ntpScope) (reterr erro s.NTP.Labels[v1alpha1.DeviceLabel] = s.Device.Name + defer func() { + conditions.RecomputeReady(s.NTP) + }() + // Ensure the NTP is owned by the Device. if !controllerutil.HasControllerReference(s.NTP) { if err := controllerutil.SetOwnerReference(s.Device, s.NTP, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { @@ -294,8 +298,6 @@ func (r *NTPReconciler) reconcile(ctx context.Context, s *ntpScope) (reterr erro }) cond := conditions.FromError(err) - // As this resource is configuration only, we use the Configured condition as top-level Ready condition. - cond.Type = v1alpha1.ReadyCondition conditions.Set(s.NTP, cond) return err diff --git a/internal/controller/core/ntp_controller_test.go b/internal/controller/core/ntp_controller_test.go index 972e9c75b..f248675fc 100644 --- a/internal/controller/core/ntp_controller_test.go +++ b/internal/controller/core/ntp_controller_test.go @@ -105,11 +105,13 @@ var _ = Describe("NTP Controller", func() { Eventually(func(g Gomega) { resource := &v1alpha1.NTP{} g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) - g.Expect(resource.Status.Conditions).To(HaveLen(2)) + g.Expect(resource.Status.Conditions).To(HaveLen(3)) g.Expect(resource.Status.Conditions[0].Type).To(Equal(v1alpha1.ReadyCondition)) g.Expect(resource.Status.Conditions[0].Status).To(Equal(metav1.ConditionTrue)) - g.Expect(resource.Status.Conditions[1].Type).To(Equal(v1alpha1.PausedCondition)) - g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionFalse)) + g.Expect(resource.Status.Conditions[1].Type).To(Equal(v1alpha1.ConfiguredCondition)) + g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionTrue)) + g.Expect(resource.Status.Conditions[2].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[2].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) By("Ensuring the resource is created in the provider") diff --git a/internal/controller/core/pim_controller.go b/internal/controller/core/pim_controller.go index 5bd8d03ea..e2eab9087 100644 --- a/internal/controller/core/pim_controller.go +++ b/internal/controller/core/pim_controller.go @@ -173,7 +173,7 @@ func (r *PIMReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl } orig := obj.DeepCopy() - if conditions.InitializeConditions(obj, v1alpha1.ReadyCondition) { + if conditions.InitializeConditions(obj, v1alpha1.ReadyCondition, v1alpha1.ConfiguredCondition) { log.V(1).Info("Initializing status conditions") return ctrl.Result{}, r.Status().Update(ctx, obj) } @@ -290,6 +290,10 @@ func (r *PIMReconciler) reconcile(ctx context.Context, s *pimScope) (reterr erro s.PIM.Labels[v1alpha1.DeviceLabel] = s.Device.Name + defer func() { + conditions.RecomputeReady(s.PIM) + }() + // Ensure the PIM is owned by the Device. if !controllerutil.HasControllerReference(s.PIM) { if err := controllerutil.SetOwnerReference(s.Device, s.PIM, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { @@ -303,7 +307,7 @@ func (r *PIMReconciler) reconcile(ctx context.Context, s *pimScope) (reterr erro if err := r.Get(ctx, client.ObjectKey{Name: intf.Name, Namespace: s.PIM.Namespace}, res); err != nil { if apierrors.IsNotFound(err) { conditions.Set(s.PIM, metav1.Condition{ - Type: v1alpha1.ReadyCondition, + Type: v1alpha1.ConfiguredCondition, Status: metav1.ConditionFalse, Reason: v1alpha1.InterfaceNotFoundReason, Message: fmt.Sprintf("interface %q not found", intf.Name), @@ -315,7 +319,7 @@ func (r *PIMReconciler) reconcile(ctx context.Context, s *pimScope) (reterr erro if !conditions.IsConfigured(res) { conditions.Set(s.PIM, metav1.Condition{ - Type: v1alpha1.ReadyCondition, + Type: v1alpha1.ConfiguredCondition, Status: metav1.ConditionFalse, Reason: v1alpha1.WaitingForDependenciesReason, Message: "Waiting for referenced interfaces to become configured", @@ -346,8 +350,6 @@ func (r *PIMReconciler) reconcile(ctx context.Context, s *pimScope) (reterr erro }) cond := conditions.FromError(err) - // As this resource is configuration only, we use the Configured condition as top-level Ready condition. - cond.Type = v1alpha1.ReadyCondition conditions.Set(s.PIM, cond) return err diff --git a/internal/controller/core/pim_controller_test.go b/internal/controller/core/pim_controller_test.go index 427e58d41..0e30865c9 100644 --- a/internal/controller/core/pim_controller_test.go +++ b/internal/controller/core/pim_controller_test.go @@ -98,11 +98,13 @@ var _ = Describe("PIM Controller", func() { Eventually(func(g Gomega) { resource := &v1alpha1.PIM{} g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) - g.Expect(resource.Status.Conditions).To(HaveLen(2)) + g.Expect(resource.Status.Conditions).To(HaveLen(3)) g.Expect(resource.Status.Conditions[0].Type).To(Equal(v1alpha1.ReadyCondition)) g.Expect(resource.Status.Conditions[0].Status).To(Equal(metav1.ConditionTrue)) - g.Expect(resource.Status.Conditions[1].Type).To(Equal(v1alpha1.PausedCondition)) - g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionFalse)) + g.Expect(resource.Status.Conditions[1].Type).To(Equal(v1alpha1.ConfiguredCondition)) + g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionTrue)) + g.Expect(resource.Status.Conditions[2].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[2].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) By("Ensuring the resource is created in the provider") @@ -150,7 +152,7 @@ var _ = Describe("PIM Controller", func() { Expect(client.IgnoreNotFound(k8sClient.Delete(ctx, device))).To(Succeed()) }) - It("Should set ReadyCondition to false when interfaceRef does not exist", func() { + It("Should set ConfiguredCondition to false when interfaceRef does not exist", func() { By("Creating a PIM resource with a non-existent interfaceRef") pim := &v1alpha1.PIM{ ObjectMeta: metav1.ObjectMeta{ @@ -169,12 +171,12 @@ var _ = Describe("PIM Controller", func() { } Expect(k8sClient.Create(ctx, pim)).To(Succeed()) - By("Verifying the controller sets ReadyCondition to false") + By("Verifying the controller sets ConfiguredCondition to false") Eventually(func(g Gomega) { resource := &v1alpha1.PIM{} g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) - ready := meta.FindStatusCondition(resource.Status.Conditions, v1alpha1.ReadyCondition) + ready := meta.FindStatusCondition(resource.Status.Conditions, v1alpha1.ConfiguredCondition) g.Expect(ready).NotTo(BeNil()) g.Expect(ready.Status).To(Equal(metav1.ConditionFalse)) g.Expect(ready.Reason).To(Equal(v1alpha1.InterfaceNotFoundReason)) diff --git a/internal/controller/core/prefixset_controller.go b/internal/controller/core/prefixset_controller.go index 73a220631..60357971a 100644 --- a/internal/controller/core/prefixset_controller.go +++ b/internal/controller/core/prefixset_controller.go @@ -172,7 +172,7 @@ func (r *PrefixSetReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( } orig := obj.DeepCopy() - if conditions.InitializeConditions(obj, v1alpha1.ReadyCondition) { + if conditions.InitializeConditions(obj, v1alpha1.ReadyCondition, v1alpha1.ConfiguredCondition) { log.V(1).Info("Initializing status conditions") return ctrl.Result{}, r.Status().Update(ctx, obj) } @@ -271,6 +271,10 @@ func (r *PrefixSetReconciler) reconcile(ctx context.Context, s *prefixSetScope) s.PrefixSet.Labels[v1alpha1.DeviceLabel] = s.Device.Name + defer func() { + conditions.RecomputeReady(s.PrefixSet) + }() + // Ensure the PrefixSet is owned by the Device. if !controllerutil.HasControllerReference(s.PrefixSet) { if err := controllerutil.SetOwnerReference(s.Device, s.PrefixSet, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { @@ -300,8 +304,6 @@ func (r *PrefixSetReconciler) reconcile(ctx context.Context, s *prefixSetScope) }) cond := conditions.FromError(err) - // As this resource is configuration only, we use the Configured condition as top-level Ready condition. - cond.Type = v1alpha1.ReadyCondition conditions.Set(s.PrefixSet, cond) return err diff --git a/internal/controller/core/prefixset_controller_test.go b/internal/controller/core/prefixset_controller_test.go index 540a886ff..f7089ff2b 100644 --- a/internal/controller/core/prefixset_controller_test.go +++ b/internal/controller/core/prefixset_controller_test.go @@ -109,11 +109,13 @@ var _ = Describe("PrefixSet Controller", func() { Eventually(func(g Gomega) { resource := &v1alpha1.PrefixSet{} g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) - g.Expect(resource.Status.Conditions).To(HaveLen(2)) + g.Expect(resource.Status.Conditions).To(HaveLen(3)) g.Expect(resource.Status.Conditions[0].Type).To(Equal(v1alpha1.ReadyCondition)) g.Expect(resource.Status.Conditions[0].Status).To(Equal(metav1.ConditionTrue)) - g.Expect(resource.Status.Conditions[1].Type).To(Equal(v1alpha1.PausedCondition)) - g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionFalse)) + g.Expect(resource.Status.Conditions[1].Type).To(Equal(v1alpha1.ConfiguredCondition)) + g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionTrue)) + g.Expect(resource.Status.Conditions[2].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[2].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) By("Ensuring the resource is created in the provider") diff --git a/internal/controller/core/routingpolicy_controller.go b/internal/controller/core/routingpolicy_controller.go index 63acc8c37..7a982023f 100644 --- a/internal/controller/core/routingpolicy_controller.go +++ b/internal/controller/core/routingpolicy_controller.go @@ -172,7 +172,7 @@ func (r *RoutingPolicyReconciler) Reconcile(ctx context.Context, req ctrl.Reques } orig := obj.DeepCopy() - if conditions.InitializeConditions(obj, v1alpha1.ReadyCondition) { + if conditions.InitializeConditions(obj, v1alpha1.ReadyCondition, v1alpha1.ConfiguredCondition) { log.V(1).Info("Initializing status conditions") return ctrl.Result{}, r.Status().Update(ctx, obj) } @@ -300,6 +300,10 @@ func (r *RoutingPolicyReconciler) reconcile(ctx context.Context, s *routingPolic s.RoutingPolicy.Labels[v1alpha1.DeviceLabel] = s.Device.Name + defer func() { + conditions.RecomputeReady(s.RoutingPolicy) + }() + // Ensure the RoutingPolicy is owned by the Device. if !controllerutil.HasControllerReference(s.RoutingPolicy) { if err := controllerutil.SetOwnerReference(s.Device, s.RoutingPolicy, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { @@ -335,8 +339,6 @@ func (r *RoutingPolicyReconciler) reconcile(ctx context.Context, s *routingPolic }) cond := conditions.FromError(err) - // As this resource is configuration only, we use the Configured condition as top-level Ready condition. - cond.Type = v1alpha1.ReadyCondition conditions.Set(s.RoutingPolicy, cond) return err @@ -378,7 +380,7 @@ func (r *RoutingPolicyReconciler) reconcilePrefixSet(ctx context.Context, s *rou if err := r.Get(ctx, key, prefixSet); err != nil { if apierrors.IsNotFound(err) { conditions.Set(s.RoutingPolicy, metav1.Condition{ - Type: v1alpha1.ReadyCondition, + Type: v1alpha1.ConfiguredCondition, Status: metav1.ConditionFalse, Reason: v1alpha1.PrefixSetNotFoundReason, Message: fmt.Sprintf("referenced PrefixSet %q not found", key), @@ -390,7 +392,7 @@ func (r *RoutingPolicyReconciler) reconcilePrefixSet(ctx context.Context, s *rou if prefixSet.Spec.DeviceRef.Name != s.Device.Name { conditions.Set(s.RoutingPolicy, metav1.Condition{ - Type: v1alpha1.ReadyCondition, + Type: v1alpha1.ConfiguredCondition, Status: metav1.ConditionFalse, Reason: v1alpha1.CrossDeviceReferenceReason, Message: fmt.Sprintf("referenced PrefixSet %q does not belong to device %q", prefixSet.Name, s.Device.Name), diff --git a/internal/controller/core/routingpolicy_controller_test.go b/internal/controller/core/routingpolicy_controller_test.go index d9579f6da..6edd353e0 100644 --- a/internal/controller/core/routingpolicy_controller_test.go +++ b/internal/controller/core/routingpolicy_controller_test.go @@ -114,11 +114,13 @@ var _ = Describe("RoutingPolicy Controller", func() { Eventually(func(g Gomega) { resource := &v1alpha1.RoutingPolicy{} g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) - g.Expect(resource.Status.Conditions).To(HaveLen(2)) + g.Expect(resource.Status.Conditions).To(HaveLen(3)) g.Expect(resource.Status.Conditions[0].Type).To(Equal(v1alpha1.ReadyCondition)) g.Expect(resource.Status.Conditions[0].Status).To(Equal(metav1.ConditionTrue)) - g.Expect(resource.Status.Conditions[1].Type).To(Equal(v1alpha1.PausedCondition)) - g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionFalse)) + g.Expect(resource.Status.Conditions[1].Type).To(Equal(v1alpha1.ConfiguredCondition)) + g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionTrue)) + g.Expect(resource.Status.Conditions[2].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[2].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) By("Ensuring the resource is created in the provider") @@ -185,11 +187,13 @@ var _ = Describe("RoutingPolicy Controller", func() { Eventually(func(g Gomega) { resource := &v1alpha1.RoutingPolicy{} g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) - g.Expect(resource.Status.Conditions).To(HaveLen(2)) + g.Expect(resource.Status.Conditions).To(HaveLen(3)) g.Expect(resource.Status.Conditions[0].Type).To(Equal(v1alpha1.ReadyCondition)) g.Expect(resource.Status.Conditions[0].Status).To(Equal(metav1.ConditionTrue)) - g.Expect(resource.Status.Conditions[1].Type).To(Equal(v1alpha1.PausedCondition)) - g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionFalse)) + g.Expect(resource.Status.Conditions[1].Type).To(Equal(v1alpha1.ConfiguredCondition)) + g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionTrue)) + g.Expect(resource.Status.Conditions[2].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[2].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) By("Verifying the RoutingPolicy is configured in the provider") @@ -229,12 +233,14 @@ var _ = Describe("RoutingPolicy Controller", func() { Eventually(func(g Gomega) { resource := &v1alpha1.RoutingPolicy{} g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) - g.Expect(resource.Status.Conditions).To(HaveLen(2)) + g.Expect(resource.Status.Conditions).To(HaveLen(3)) g.Expect(resource.Status.Conditions[0].Type).To(Equal(v1alpha1.ReadyCondition)) g.Expect(resource.Status.Conditions[0].Status).To(Equal(metav1.ConditionFalse)) - g.Expect(resource.Status.Conditions[0].Reason).To(Equal(v1alpha1.PrefixSetNotFoundReason)) - g.Expect(resource.Status.Conditions[1].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[1].Type).To(Equal(v1alpha1.ConfiguredCondition)) g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionFalse)) + g.Expect(resource.Status.Conditions[1].Reason).To(Equal(v1alpha1.PrefixSetNotFoundReason)) + g.Expect(resource.Status.Conditions[2].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[2].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) }) @@ -334,11 +340,13 @@ var _ = Describe("RoutingPolicy Controller", func() { Eventually(func(g Gomega) { resource := &v1alpha1.RoutingPolicy{} g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) - g.Expect(resource.Status.Conditions).To(HaveLen(2)) + g.Expect(resource.Status.Conditions).To(HaveLen(3)) g.Expect(resource.Status.Conditions[0].Type).To(Equal(v1alpha1.ReadyCondition)) g.Expect(resource.Status.Conditions[0].Status).To(Equal(metav1.ConditionTrue)) - g.Expect(resource.Status.Conditions[1].Type).To(Equal(v1alpha1.PausedCondition)) - g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionFalse)) + g.Expect(resource.Status.Conditions[1].Type).To(Equal(v1alpha1.ConfiguredCondition)) + g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionTrue)) + g.Expect(resource.Status.Conditions[2].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[2].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) By("Verifying the RoutingPolicy is configured in the provider") @@ -397,12 +405,14 @@ var _ = Describe("RoutingPolicy Controller", func() { Eventually(func(g Gomega) { resource := &v1alpha1.RoutingPolicy{} g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) - g.Expect(resource.Status.Conditions).To(HaveLen(2)) + g.Expect(resource.Status.Conditions).To(HaveLen(3)) g.Expect(resource.Status.Conditions[0].Type).To(Equal(v1alpha1.ReadyCondition)) g.Expect(resource.Status.Conditions[0].Status).To(Equal(metav1.ConditionFalse)) - g.Expect(resource.Status.Conditions[0].Reason).To(Equal(v1alpha1.CrossDeviceReferenceReason)) - g.Expect(resource.Status.Conditions[1].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[1].Type).To(Equal(v1alpha1.ConfiguredCondition)) g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionFalse)) + g.Expect(resource.Status.Conditions[1].Reason).To(Equal(v1alpha1.CrossDeviceReferenceReason)) + g.Expect(resource.Status.Conditions[2].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[2].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) }) }) diff --git a/internal/controller/core/snmp_controller.go b/internal/controller/core/snmp_controller.go index 2c2012e62..e753aa782 100644 --- a/internal/controller/core/snmp_controller.go +++ b/internal/controller/core/snmp_controller.go @@ -172,7 +172,7 @@ func (r *SNMPReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctr } orig := obj.DeepCopy() - if conditions.InitializeConditions(obj, v1alpha1.ReadyCondition) { + if conditions.InitializeConditions(obj, v1alpha1.ReadyCondition, v1alpha1.ConfiguredCondition) { log.V(1).Info("Initializing status conditions") return ctrl.Result{}, r.Status().Update(ctx, obj) } @@ -271,6 +271,10 @@ func (r *SNMPReconciler) reconcile(ctx context.Context, s *snmpScope) (reterr er s.SNMP.Labels[v1alpha1.DeviceLabel] = s.Device.Name + defer func() { + conditions.RecomputeReady(s.SNMP) + }() + // Ensure the SNMP is owned by the Device. if !controllerutil.HasControllerReference(s.SNMP) { if err := controllerutil.SetOwnerReference(s.Device, s.SNMP, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { @@ -294,8 +298,6 @@ func (r *SNMPReconciler) reconcile(ctx context.Context, s *snmpScope) (reterr er }) cond := conditions.FromError(err) - // As this resource is configuration only, we use the Configured condition as top-level Ready condition. - cond.Type = v1alpha1.ReadyCondition conditions.Set(s.SNMP, cond) return err diff --git a/internal/controller/core/snmp_controller_test.go b/internal/controller/core/snmp_controller_test.go index 145dfee47..31d39af37 100644 --- a/internal/controller/core/snmp_controller_test.go +++ b/internal/controller/core/snmp_controller_test.go @@ -116,11 +116,13 @@ var _ = Describe("SNMP Controller", func() { Eventually(func(g Gomega) { resource := &v1alpha1.SNMP{} g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) - g.Expect(resource.Status.Conditions).To(HaveLen(2)) + g.Expect(resource.Status.Conditions).To(HaveLen(3)) g.Expect(resource.Status.Conditions[0].Type).To(Equal(v1alpha1.ReadyCondition)) g.Expect(resource.Status.Conditions[0].Status).To(Equal(metav1.ConditionTrue)) - g.Expect(resource.Status.Conditions[1].Type).To(Equal(v1alpha1.PausedCondition)) - g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionFalse)) + g.Expect(resource.Status.Conditions[1].Type).To(Equal(v1alpha1.ConfiguredCondition)) + g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionTrue)) + g.Expect(resource.Status.Conditions[2].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[2].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) By("Ensuring the resource is created in the provider") diff --git a/internal/controller/core/syslog_controller.go b/internal/controller/core/syslog_controller.go index 12d8dd364..edf864d9d 100644 --- a/internal/controller/core/syslog_controller.go +++ b/internal/controller/core/syslog_controller.go @@ -172,7 +172,7 @@ func (r *SyslogReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ c } orig := obj.DeepCopy() - if conditions.InitializeConditions(obj, v1alpha1.ReadyCondition) { + if conditions.InitializeConditions(obj, v1alpha1.ReadyCondition, v1alpha1.ConfiguredCondition) { log.V(1).Info("Initializing status conditions") return ctrl.Result{}, r.Status().Update(ctx, obj) } @@ -271,6 +271,10 @@ func (r *SyslogReconciler) reconcile(ctx context.Context, s *syslogScope) (reter s.Syslog.Labels[v1alpha1.DeviceLabel] = s.Device.Name + defer func() { + conditions.RecomputeReady(s.Syslog) + }() + // Ensure the Syslog is owned by the Device. if !controllerutil.HasControllerReference(s.Syslog) { if err := controllerutil.SetOwnerReference(s.Device, s.Syslog, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { @@ -300,8 +304,6 @@ func (r *SyslogReconciler) reconcile(ctx context.Context, s *syslogScope) (reter }) cond := conditions.FromError(err) - // As this resource is configuration only, we use the Configured condition as top-level Ready condition. - cond.Type = v1alpha1.ReadyCondition conditions.Set(s.Syslog, cond) return err diff --git a/internal/controller/core/syslog_controller_test.go b/internal/controller/core/syslog_controller_test.go index 8e477ca06..4a2e38b02 100644 --- a/internal/controller/core/syslog_controller_test.go +++ b/internal/controller/core/syslog_controller_test.go @@ -111,11 +111,13 @@ var _ = Describe("Syslog Controller", func() { Eventually(func(g Gomega) { resource := &v1alpha1.Syslog{} g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) - g.Expect(resource.Status.Conditions).To(HaveLen(2)) + g.Expect(resource.Status.Conditions).To(HaveLen(3)) g.Expect(resource.Status.Conditions[0].Type).To(Equal(v1alpha1.ReadyCondition)) g.Expect(resource.Status.Conditions[0].Status).To(Equal(metav1.ConditionTrue)) - g.Expect(resource.Status.Conditions[1].Type).To(Equal(v1alpha1.PausedCondition)) - g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionFalse)) + g.Expect(resource.Status.Conditions[1].Type).To(Equal(v1alpha1.ConfiguredCondition)) + g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionTrue)) + g.Expect(resource.Status.Conditions[2].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[2].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) By("Ensuring the resource is created in the provider") diff --git a/internal/controller/core/user_controller.go b/internal/controller/core/user_controller.go index ff16800a3..7ac85b211 100644 --- a/internal/controller/core/user_controller.go +++ b/internal/controller/core/user_controller.go @@ -175,7 +175,7 @@ func (r *UserReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctr } orig := obj.DeepCopy() - if conditions.InitializeConditions(obj, v1alpha1.ReadyCondition) { + if conditions.InitializeConditions(obj, v1alpha1.ReadyCondition, v1alpha1.ConfiguredCondition) { log.V(1).Info("Initializing status conditions") return ctrl.Result{}, r.Status().Update(ctx, obj) } @@ -280,6 +280,10 @@ func (r *UserReconciler) reconcile(ctx context.Context, s *userScope) (reterr er s.User.Labels[v1alpha1.DeviceLabel] = s.Device.Name + defer func() { + conditions.RecomputeReady(s.User) + }() + // Ensure the User is owned by the Device. if !controllerutil.HasControllerReference(s.User) { if err := controllerutil.SetOwnerReference(s.Device, s.User, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { @@ -326,8 +330,6 @@ func (r *UserReconciler) reconcile(ctx context.Context, s *userScope) (reterr er }) cond := conditions.FromError(err) - // As this resource is configuration only, we use the Configured condition as top-level Ready condition. - cond.Type = v1alpha1.ReadyCondition conditions.Set(s.User, cond) return err diff --git a/internal/controller/core/user_controller_test.go b/internal/controller/core/user_controller_test.go index 94cf845e6..1492eb2f3 100644 --- a/internal/controller/core/user_controller_test.go +++ b/internal/controller/core/user_controller_test.go @@ -121,11 +121,13 @@ var _ = Describe("User Controller", func() { Eventually(func(g Gomega) { resource := &v1alpha1.User{} g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) - g.Expect(resource.Status.Conditions).To(HaveLen(2)) + g.Expect(resource.Status.Conditions).To(HaveLen(3)) g.Expect(resource.Status.Conditions[0].Type).To(Equal(v1alpha1.ReadyCondition)) g.Expect(resource.Status.Conditions[0].Status).To(Equal(metav1.ConditionTrue)) - g.Expect(resource.Status.Conditions[1].Type).To(Equal(v1alpha1.PausedCondition)) - g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionFalse)) + g.Expect(resource.Status.Conditions[1].Type).To(Equal(v1alpha1.ConfiguredCondition)) + g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionTrue)) + g.Expect(resource.Status.Conditions[2].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[2].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) By("Ensuring the resource is created in the provider") diff --git a/internal/controller/core/vrf_controller.go b/internal/controller/core/vrf_controller.go index 05a1cb30a..163d0e689 100644 --- a/internal/controller/core/vrf_controller.go +++ b/internal/controller/core/vrf_controller.go @@ -174,7 +174,7 @@ func (r *VRFReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl } orig := obj.DeepCopy() - if conditions.InitializeConditions(obj, v1alpha1.ReadyCondition) { + if conditions.InitializeConditions(obj, v1alpha1.ReadyCondition, v1alpha1.ConfiguredCondition) { log.V(1).Info("Initializing status conditions") return ctrl.Result{}, r.Status().Update(ctx, obj) } @@ -219,6 +219,10 @@ func (r *VRFReconciler) reconcile(ctx context.Context, s *vrfScope) (reterr erro } s.VRF.Labels[v1alpha1.DeviceLabel] = s.Device.Name + defer func() { + conditions.RecomputeReady(s.VRF) + }() + // Ensure the VRF is owned by the Device. if !controllerutil.HasControllerReference(s.VRF) { if err := controllerutil.SetOwnerReference(s.Device, s.VRF, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { @@ -243,8 +247,6 @@ func (r *VRFReconciler) reconcile(ctx context.Context, s *vrfScope) (reterr erro }) cond := conditions.FromError(err) - // As this resource is configuration only, we use the Configured condition as top-level Ready condition. - cond.Type = v1alpha1.ReadyCondition conditions.Set(s.VRF, cond) return err diff --git a/internal/controller/core/vrf_controller_test.go b/internal/controller/core/vrf_controller_test.go index e9d76ff51..caa4d1eb1 100644 --- a/internal/controller/core/vrf_controller_test.go +++ b/internal/controller/core/vrf_controller_test.go @@ -115,11 +115,13 @@ var _ = Describe("VRF Controller", func() { By("Updating the resource status") Eventually(func(g Gomega) { g.Expect(k8sClient.Get(ctx, key, vrf)).To(Succeed()) - g.Expect(vrf.Status.Conditions).To(HaveLen(2)) + g.Expect(vrf.Status.Conditions).To(HaveLen(3)) g.Expect(vrf.Status.Conditions[0].Type).To(Equal(v1alpha1.ReadyCondition)) g.Expect(vrf.Status.Conditions[0].Status).To(Equal(metav1.ConditionTrue)) - g.Expect(vrf.Status.Conditions[1].Type).To(Equal(v1alpha1.PausedCondition)) - g.Expect(vrf.Status.Conditions[1].Status).To(Equal(metav1.ConditionFalse)) + g.Expect(vrf.Status.Conditions[1].Type).To(Equal(v1alpha1.ConfiguredCondition)) + g.Expect(vrf.Status.Conditions[1].Status).To(Equal(metav1.ConditionTrue)) + g.Expect(vrf.Status.Conditions[2].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(vrf.Status.Conditions[2].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) By("Ensuring the VRF is created in the provider") From 9152ee444a99231f4af04a96168addf144292f69 Mon Sep 17 00:00:00 2001 From: Adam Trizuljak Date: Fri, 24 Jul 2026 11:53:02 +0200 Subject: [PATCH 02/12] WIP improving test reliability by setting Configured condition Signed-off-by: Adam Trizuljak --- internal/controller/core/bgp_controller.go | 5 +++++ internal/controller/core/bgp_controller_test.go | 7 +++++++ internal/controller/core/bgp_peer_controller.go | 5 +++++ .../controller/core/bgp_peer_controller_test.go | 10 +++++----- internal/controller/core/dhcprelay_controller.go | 5 +++++ .../controller/core/dhcprelay_controller_test.go | 15 +++++++++++++++ .../controller/core/routingpolicy_controller.go | 5 +++++ .../core/routingpolicy_controller_test.go | 8 ++++++++ internal/controller/core/vlan_controller.go | 5 +++++ internal/controller/core/vlan_controller_test.go | 8 ++++++++ internal/controller/core/vrf_controller.go | 5 +++++ 11 files changed, 73 insertions(+), 5 deletions(-) diff --git a/internal/controller/core/bgp_controller.go b/internal/controller/core/bgp_controller.go index 7e9a9893e..2482fd3ed 100644 --- a/internal/controller/core/bgp_controller.go +++ b/internal/controller/core/bgp_controller.go @@ -352,6 +352,11 @@ func (r *BGPReconciler) reconcile(ctx context.Context, s *bgpScope) (reterr erro conditions.RecomputeReady(s.BGP) }() + conditions.Set(s.BGP, metav1.Condition{ + Type: v1alpha1.ConfiguredCondition, + Status: metav1.ConditionFalse, + }) + // Ensure the BGP is owned by the Device. if !controllerutil.HasControllerReference(s.BGP) { if err := controllerutil.SetOwnerReference(s.Device, s.BGP, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { diff --git a/internal/controller/core/bgp_controller_test.go b/internal/controller/core/bgp_controller_test.go index 6a9bb6342..1ea78b20c 100644 --- a/internal/controller/core/bgp_controller_test.go +++ b/internal/controller/core/bgp_controller_test.go @@ -146,6 +146,13 @@ var _ = Describe("BGP Controller", func() { } Expect(k8sClient.Create(ctx, bgp)).To(Succeed()) + By("Waiting for BGP's condition to be fully consistent") + Eventually(func(g Gomega) { + resource := &v1alpha1.BGP{} + g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(bgp), resource)).To(Succeed()) + g.Expect(conditions.IsConfigured(resource)).To(BeFalse()) // checks ObservedGeneration too + }).Should(Succeed()) + By("Expecting ConfiguredCondition to be False with VRFNotFoundReason reason") Eventually(func(g Gomega) { resource := &v1alpha1.BGP{} diff --git a/internal/controller/core/bgp_peer_controller.go b/internal/controller/core/bgp_peer_controller.go index 201ca4fb4..47f0894ea 100644 --- a/internal/controller/core/bgp_peer_controller.go +++ b/internal/controller/core/bgp_peer_controller.go @@ -382,6 +382,11 @@ func (r *BGPPeerReconciler) reconcile(ctx context.Context, s *bgpPeerScope) (ret conditions.RecomputeReady(s.BGPPeer) }() + conditions.Set(s.BGPPeer, metav1.Condition{ + Type: v1alpha1.ConfiguredCondition, + Status: metav1.ConditionFalse, + }) + bgp, err := r.reconcileBGP(ctx, s.BGPPeer, s.Device) if err != nil { return err diff --git a/internal/controller/core/bgp_peer_controller_test.go b/internal/controller/core/bgp_peer_controller_test.go index 767ad455e..cebe4eb78 100644 --- a/internal/controller/core/bgp_peer_controller_test.go +++ b/internal/controller/core/bgp_peer_controller_test.go @@ -93,7 +93,7 @@ var _ = Describe("BGPPeer Controller", func() { Eventually(func(g Gomega) { b := &v1alpha1.BGP{} g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(bgp), b)).To(Succeed()) - g.Expect(conditions.IsReady(b)).To(BeTrue()) + g.Expect(conditions.IsConfigured(b)).To(BeTrue()) }).Should(Succeed()) By("Creating a BGPPeer resource") @@ -250,7 +250,7 @@ var _ = Describe("BGPPeer Controller", func() { Eventually(func(g Gomega) { b := &v1alpha1.BGP{} g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(bgp), b)).To(Succeed()) - g.Expect(conditions.IsReady(b)).To(BeTrue()) + g.Expect(conditions.IsConfigured(b)).To(BeTrue()) }).Should(Succeed()) By("Creating a BGPPeer resource with LocalAddress pointing to a non-existent Interface") @@ -315,7 +315,7 @@ var _ = Describe("BGPPeer Controller", func() { Eventually(func(g Gomega) { b := &v1alpha1.BGP{} g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(bgp), b)).To(Succeed()) - g.Expect(conditions.IsReady(b)).To(BeTrue()) + g.Expect(conditions.IsConfigured(b)).To(BeTrue()) }).Should(Succeed()) By("Creating a Loopback Interface resource on a different device") @@ -462,7 +462,7 @@ var _ = Describe("BGPPeer Controller", func() { }) It("Should not reconcile iBGP peer if local-as is set", func() { - By("Creating a BGP resource for the Device") + // By("Creating a BGP resource for the Device") By("Creating a BGP resource for the Device") bgp := &v1alpha1.BGP{ ObjectMeta: metav1.ObjectMeta{ @@ -481,7 +481,7 @@ var _ = Describe("BGPPeer Controller", func() { Eventually(func(g Gomega) { b := &v1alpha1.BGP{} g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(bgp), b)).To(Succeed()) - g.Expect(conditions.IsReady(b)).To(BeTrue()) + g.Expect(conditions.IsConfigured(b)).To(BeTrue()) }).Should(Succeed()) By("Creating a BGPPeer resource") diff --git a/internal/controller/core/dhcprelay_controller.go b/internal/controller/core/dhcprelay_controller.go index 7f6e7488c..b8732fe47 100644 --- a/internal/controller/core/dhcprelay_controller.go +++ b/internal/controller/core/dhcprelay_controller.go @@ -227,6 +227,11 @@ func (r *DHCPRelayReconciler) reconcile(ctx context.Context, s *dhcprelayScope) conditions.RecomputeReady(s.DHCPRelay) }() + conditions.Set(s.DHCPRelay, metav1.Condition{ + Type: v1alpha1.ConfiguredCondition, + Status: metav1.ConditionFalse, + }) + // Ensure the DHCPRelay is owned by the Device. if !controllerutil.HasControllerReference(s.DHCPRelay) { if err := controllerutil.SetOwnerReference(s.Device, s.DHCPRelay, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { diff --git a/internal/controller/core/dhcprelay_controller_test.go b/internal/controller/core/dhcprelay_controller_test.go index 738357138..a7e447221 100644 --- a/internal/controller/core/dhcprelay_controller_test.go +++ b/internal/controller/core/dhcprelay_controller_test.go @@ -16,6 +16,7 @@ import ( "k8s.io/apimachinery/pkg/api/meta" "github.com/ironcore-dev/network-operator/api/core/v1alpha1" + "github.com/ironcore-dev/network-operator/internal/conditions" ) var _ = Describe("DHCPRelay Controller", func() { @@ -1126,6 +1127,13 @@ var _ = Describe("DHCPRelay Controller", func() { resourceName = dhcprelay.Name resourceKey = client.ObjectKey{Name: resourceName, Namespace: metav1.NamespaceDefault} + By("Waiting for DHCPRelay's condition to be fully consistent") + Eventually(func(g Gomega) { + resource := &v1alpha1.DHCPRelay{} + g.Expect(k8sClient.Get(ctx, resourceKey, resource)).To(Succeed()) + g.Expect(conditions.IsConfigured(resource)).To(BeFalse()) // checks ObservedGeneration too + }).Should(Succeed()) + By("Verifying DHCPRelay is not ready due to non-configured Interface") Eventually(func(g Gomega) { err := k8sClient.Get(ctx, resourceKey, dhcprelay) @@ -1159,6 +1167,13 @@ var _ = Describe("DHCPRelay Controller", func() { g.Expect(cond.Status).To(Equal(metav1.ConditionTrue)) }).Should(Succeed()) + By("Waiting for Interface's condition to be fully consistent") + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, interfaceKey, intf) + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(conditions.IsConfigured(intf)).To(BeTrue()) // checks ObservedGeneration too + }).Should(Succeed()) + By("Verifying DHCPRelay becomes ready after Interface is configured (watch triggered re-reconciliation)") Eventually(func(g Gomega) { err := k8sClient.Get(ctx, resourceKey, dhcprelay) diff --git a/internal/controller/core/routingpolicy_controller.go b/internal/controller/core/routingpolicy_controller.go index 7a982023f..0b60853c5 100644 --- a/internal/controller/core/routingpolicy_controller.go +++ b/internal/controller/core/routingpolicy_controller.go @@ -304,6 +304,11 @@ func (r *RoutingPolicyReconciler) reconcile(ctx context.Context, s *routingPolic conditions.RecomputeReady(s.RoutingPolicy) }() + conditions.Set(s.RoutingPolicy, metav1.Condition{ + Type: v1alpha1.ConfiguredCondition, + Status: metav1.ConditionFalse, + }) + // Ensure the RoutingPolicy is owned by the Device. if !controllerutil.HasControllerReference(s.RoutingPolicy) { if err := controllerutil.SetOwnerReference(s.Device, s.RoutingPolicy, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { diff --git a/internal/controller/core/routingpolicy_controller_test.go b/internal/controller/core/routingpolicy_controller_test.go index 6edd353e0..f2c791cbc 100644 --- a/internal/controller/core/routingpolicy_controller_test.go +++ b/internal/controller/core/routingpolicy_controller_test.go @@ -14,6 +14,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "github.com/ironcore-dev/network-operator/api/core/v1alpha1" + "github.com/ironcore-dev/network-operator/internal/conditions" ) var _ = Describe("RoutingPolicy Controller", func() { @@ -183,6 +184,13 @@ var _ = Describe("RoutingPolicy Controller", func() { } Expect(k8sClient.Create(ctx, rp)).To(Succeed()) + By("Waiting for RoutingPolicy's condition to be fully consistent") + Eventually(func(g Gomega) { + resource := &v1alpha1.RoutingPolicy{} + g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) + g.Expect(conditions.IsConfigured(resource)).To(BeFalse()) // checks ObservedGeneration too + }).Should(Succeed()) + By("Verifying the controller sets successful status conditions") Eventually(func(g Gomega) { resource := &v1alpha1.RoutingPolicy{} diff --git a/internal/controller/core/vlan_controller.go b/internal/controller/core/vlan_controller.go index c86866302..ef5c7b61b 100644 --- a/internal/controller/core/vlan_controller.go +++ b/internal/controller/core/vlan_controller.go @@ -290,6 +290,11 @@ func (r *VLANReconciler) reconcile(ctx context.Context, s *vlanScope) (reterr er conditions.RecomputeReady(s.VLAN) }() + conditions.Set(s.VLAN, metav1.Condition{ + Type: v1alpha1.ConfiguredCondition, + Status: metav1.ConditionFalse, + }) + if err := s.Provider.Connect(ctx, s.Connection); err != nil { return fmt.Errorf("failed to connect to provider: %w", err) } diff --git a/internal/controller/core/vlan_controller_test.go b/internal/controller/core/vlan_controller_test.go index edc1b8cd5..6600be88a 100644 --- a/internal/controller/core/vlan_controller_test.go +++ b/internal/controller/core/vlan_controller_test.go @@ -11,6 +11,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "github.com/ironcore-dev/network-operator/api/core/v1alpha1" + "github.com/ironcore-dev/network-operator/internal/conditions" ) var _ = Describe("VLAN Controller", func() { @@ -97,6 +98,13 @@ var _ = Describe("VLAN Controller", func() { g.Expect(resource.OwnerReferences[0].Name).To(Equal(name)) }).Should(Succeed()) + By("Waiting for VLAN's condition to be fully consistent") + Eventually(func(g Gomega) { + resource := &v1alpha1.VLAN{} + g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) + g.Expect(conditions.IsConfigured(resource)).To(BeTrue()) // checks ObservedGeneration too + }).Should(Succeed()) + By("Updating the resource status") Eventually(func(g Gomega) { resource := &v1alpha1.VLAN{} diff --git a/internal/controller/core/vrf_controller.go b/internal/controller/core/vrf_controller.go index 163d0e689..8eebf6b31 100644 --- a/internal/controller/core/vrf_controller.go +++ b/internal/controller/core/vrf_controller.go @@ -223,6 +223,11 @@ func (r *VRFReconciler) reconcile(ctx context.Context, s *vrfScope) (reterr erro conditions.RecomputeReady(s.VRF) }() + conditions.Set(s.VRF, metav1.Condition{ + Type: v1alpha1.ConfiguredCondition, + Status: metav1.ConditionFalse, + }) + // Ensure the VRF is owned by the Device. if !controllerutil.HasControllerReference(s.VRF) { if err := controllerutil.SetOwnerReference(s.Device, s.VRF, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { From 0e3c6fd7f8c7f5eff1789d29cb3c493f34e5f586 Mon Sep 17 00:00:00 2001 From: Adam Trizuljak Date: Tue, 28 Jul 2026 15:20:26 +0200 Subject: [PATCH 03/12] Add RequeueInterval to EthSegment controller Signed-off-by: Adam Trizuljak --- internal/controller/core/suite_test.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/internal/controller/core/suite_test.go b/internal/controller/core/suite_test.go index 67e774cc9..f3e3a679a 100644 --- a/internal/controller/core/suite_test.go +++ b/internal/controller/core/suite_test.go @@ -343,12 +343,13 @@ var _ = BeforeSuite(func() { }).SetupWithManager(ctx, k8sManager) Expect(err).NotTo(HaveOccurred()) - err = (&ConfigBackupReconciler{ - Client: k8sManager.GetClient(), - Scheme: k8sManager.GetScheme(), - Recorder: recorder, - Provider: prov, - Locker: testLocker, + err = (&EthernetSegmentReconciler{ + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + Recorder: recorder, + Provider: prov, + Locker: testLocker, + RequeueInterval: time.Second, }).SetupWithManager(ctx, k8sManager) Expect(err).NotTo(HaveOccurred()) From ffa8dccbb2a015f2eff64574c3f92155c62c4125 Mon Sep 17 00:00:00 2001 From: Adam Trizuljak Date: Tue, 28 Jul 2026 15:21:14 +0200 Subject: [PATCH 04/12] Set configured condition reason in BGP controller Signed-off-by: Adam Trizuljak --- internal/controller/core/bgp_controller.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/internal/controller/core/bgp_controller.go b/internal/controller/core/bgp_controller.go index 2482fd3ed..288ae1cd2 100644 --- a/internal/controller/core/bgp_controller.go +++ b/internal/controller/core/bgp_controller.go @@ -353,8 +353,10 @@ func (r *BGPReconciler) reconcile(ctx context.Context, s *bgpScope) (reterr erro }() conditions.Set(s.BGP, metav1.Condition{ - Type: v1alpha1.ConfiguredCondition, - Status: metav1.ConditionFalse, + Type: v1alpha1.ConfiguredCondition, + Status: metav1.ConditionFalse, + Reason: v1alpha1.ReconcilePendingReason, + Message: "Reconciliation is in progress", }) // Ensure the BGP is owned by the Device. From 6d40b99717da797337a005549fa20a2e8be9a9f4 Mon Sep 17 00:00:00 2001 From: Adam Trizuljak Date: Thu, 30 Jul 2026 15:33:00 +0200 Subject: [PATCH 05/12] Initialize Configured condition to False at the start of reconciliation 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 --- internal/controller/cisco/nx/bordergateway_controller.go | 7 +++++++ internal/controller/cisco/nx/system_controller.go | 7 +++++++ internal/controller/cisco/nx/vpcdomain_controller.go | 7 +++++++ internal/controller/core/aaa_controller.go | 7 +++++++ internal/controller/core/acl_controller.go | 7 +++++++ internal/controller/core/banner_controller.go | 7 +++++++ internal/controller/core/bgp_peer_controller.go | 6 ++++-- internal/controller/core/certificate_controller.go | 7 +++++++ internal/controller/core/dhcprelay_controller.go | 6 ++++-- internal/controller/core/dns_controller.go | 7 +++++++ internal/controller/core/ethernetsegment_controller.go | 7 +++++++ internal/controller/core/evpninstance_controller.go | 7 +++++++ internal/controller/core/isis_controller.go | 7 +++++++ internal/controller/core/lldp_controller.go | 9 ++++++++- internal/controller/core/managementaccess_controller.go | 7 +++++++ internal/controller/core/ntp_controller.go | 7 +++++++ internal/controller/core/nve_controller.go | 9 ++++++++- internal/controller/core/ospf_controller.go | 7 +++++++ internal/controller/core/pim_controller.go | 7 +++++++ internal/controller/core/prefixset_controller.go | 7 +++++++ internal/controller/core/routingpolicy_controller.go | 6 ++++-- internal/controller/core/snmp_controller.go | 7 +++++++ internal/controller/core/suite_test.go | 7 ++++--- internal/controller/core/syslog_controller.go | 7 +++++++ internal/controller/core/user_controller.go | 7 +++++++ internal/controller/core/vlan_controller.go | 6 ++++-- internal/controller/core/vrf_controller.go | 6 ++++-- 27 files changed, 173 insertions(+), 15 deletions(-) diff --git a/internal/controller/cisco/nx/bordergateway_controller.go b/internal/controller/cisco/nx/bordergateway_controller.go index 44d77dae1..804bf3a67 100644 --- a/internal/controller/cisco/nx/bordergateway_controller.go +++ b/internal/controller/cisco/nx/bordergateway_controller.go @@ -331,6 +331,13 @@ func (r *BorderGatewayReconciler) reconcile(ctx context.Context, s *borderGatewa conditions.RecomputeReady(s.BorderGateway) }() + conditions.Set(s.BorderGateway, metav1.Condition{ + Type: v1alpha1.ConfiguredCondition, + Status: metav1.ConditionFalse, + Reason: v1alpha1.ReconcilePendingReason, + Message: "Reconciliation is in progress", + }) + // Ensure the BorderGateway is owned by the Device. if !controllerutil.HasControllerReference(s.BorderGateway) { if err := controllerutil.SetOwnerReference(s.Device, s.BorderGateway, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { diff --git a/internal/controller/cisco/nx/system_controller.go b/internal/controller/cisco/nx/system_controller.go index 6b4735027..82bcc986d 100644 --- a/internal/controller/cisco/nx/system_controller.go +++ b/internal/controller/cisco/nx/system_controller.go @@ -251,6 +251,13 @@ func (r *SystemReconciler) reconcile(ctx context.Context, s *systemScope) (reter conditions.RecomputeReady(s.System) }() + conditions.Set(s.System, metav1.Condition{ + Type: v1alpha1.ConfiguredCondition, + Status: metav1.ConditionFalse, + Reason: v1alpha1.ReconcilePendingReason, + Message: "Reconciliation is in progress", + }) + // Ensure the System is owned by the Device. if !controllerutil.HasControllerReference(s.System) { if err := controllerutil.SetOwnerReference(s.Device, s.System, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { diff --git a/internal/controller/cisco/nx/vpcdomain_controller.go b/internal/controller/cisco/nx/vpcdomain_controller.go index a6320c5f0..ddb31d61e 100644 --- a/internal/controller/cisco/nx/vpcdomain_controller.go +++ b/internal/controller/cisco/nx/vpcdomain_controller.go @@ -297,6 +297,13 @@ func (r *VPCDomainReconciler) reconcile(ctx context.Context, s *vpcdomainScope) conditions.RecomputeReady(s.VPCDomain) }() + conditions.Set(s.VPCDomain, metav1.Condition{ + Type: v1alpha1.ConfiguredCondition, + Status: metav1.ConditionFalse, + Reason: v1alpha1.ReconcilePendingReason, + Message: "Reconciliation is in progress", + }) + peerLink, err := r.reconcilePeerLink(ctx, s) if err != nil { reterr = kerrors.NewAggregate([]error{reterr, fmt.Errorf("failed to reconcile referenced resource: %w", err)}) diff --git a/internal/controller/core/aaa_controller.go b/internal/controller/core/aaa_controller.go index fdb866512..9e3e90d80 100644 --- a/internal/controller/core/aaa_controller.go +++ b/internal/controller/core/aaa_controller.go @@ -246,6 +246,13 @@ func (r *AAAReconciler) reconcile(ctx context.Context, s *aaaScope) (reterr erro conditions.RecomputeReady(s.AAA) }() + conditions.Set(s.AAA, metav1.Condition{ + Type: v1alpha1.ConfiguredCondition, + Status: metav1.ConditionFalse, + Reason: v1alpha1.ReconcilePendingReason, + Message: "Reconciliation is in progress", + }) + // Ensure the AAA is owned by the Device. if !controllerutil.HasControllerReference(s.AAA) { if err := controllerutil.SetOwnerReference(s.Device, s.AAA, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { diff --git a/internal/controller/core/acl_controller.go b/internal/controller/core/acl_controller.go index b3a39c971..25075dfb1 100644 --- a/internal/controller/core/acl_controller.go +++ b/internal/controller/core/acl_controller.go @@ -275,6 +275,13 @@ func (r *AccessControlListReconciler) reconcile(ctx context.Context, s *aclScope conditions.RecomputeReady(s.ACL) }() + conditions.Set(s.ACL, metav1.Condition{ + Type: v1alpha1.ConfiguredCondition, + Status: metav1.ConditionFalse, + Reason: v1alpha1.ReconcilePendingReason, + Message: "Reconciliation is in progress", + }) + // Ensure the AccessControlList is owned by the Device. if !controllerutil.HasControllerReference(s.ACL) { if err := controllerutil.SetOwnerReference(s.Device, s.ACL, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { diff --git a/internal/controller/core/banner_controller.go b/internal/controller/core/banner_controller.go index 9aaf4f770..5e2936c16 100644 --- a/internal/controller/core/banner_controller.go +++ b/internal/controller/core/banner_controller.go @@ -291,6 +291,13 @@ func (r *BannerReconciler) reconcile(ctx context.Context, s *bannerScope) (reter conditions.RecomputeReady(s.Banner) }() + conditions.Set(s.Banner, metav1.Condition{ + Type: v1alpha1.ConfiguredCondition, + Status: metav1.ConditionFalse, + Reason: v1alpha1.ReconcilePendingReason, + Message: "Reconciliation is in progress", + }) + // Ensure the Banner is owned by the Device. if !controllerutil.HasControllerReference(s.Banner) { if err := controllerutil.SetOwnerReference(s.Device, s.Banner, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { diff --git a/internal/controller/core/bgp_peer_controller.go b/internal/controller/core/bgp_peer_controller.go index 47f0894ea..b49823567 100644 --- a/internal/controller/core/bgp_peer_controller.go +++ b/internal/controller/core/bgp_peer_controller.go @@ -383,8 +383,10 @@ func (r *BGPPeerReconciler) reconcile(ctx context.Context, s *bgpPeerScope) (ret }() conditions.Set(s.BGPPeer, metav1.Condition{ - Type: v1alpha1.ConfiguredCondition, - Status: metav1.ConditionFalse, + Type: v1alpha1.ConfiguredCondition, + Status: metav1.ConditionFalse, + Reason: v1alpha1.ReconcilePendingReason, + Message: "Reconciliation is in progress", }) bgp, err := r.reconcileBGP(ctx, s.BGPPeer, s.Device) diff --git a/internal/controller/core/certificate_controller.go b/internal/controller/core/certificate_controller.go index ff88d4257..5166b820c 100644 --- a/internal/controller/core/certificate_controller.go +++ b/internal/controller/core/certificate_controller.go @@ -284,6 +284,13 @@ func (r *CertificateReconciler) reconcile(ctx context.Context, s *certificateSco conditions.RecomputeReady(s.Certificate) }() + conditions.Set(s.Certificate, metav1.Condition{ + Type: v1alpha1.ConfiguredCondition, + Status: metav1.ConditionFalse, + Reason: v1alpha1.ReconcilePendingReason, + Message: "Reconciliation is in progress", + }) + // Ensure the Certificate is owned by the Device. if !controllerutil.HasControllerReference(s.Certificate) { if err := controllerutil.SetOwnerReference(s.Device, s.Certificate, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { diff --git a/internal/controller/core/dhcprelay_controller.go b/internal/controller/core/dhcprelay_controller.go index b8732fe47..29bbb9f64 100644 --- a/internal/controller/core/dhcprelay_controller.go +++ b/internal/controller/core/dhcprelay_controller.go @@ -228,8 +228,10 @@ func (r *DHCPRelayReconciler) reconcile(ctx context.Context, s *dhcprelayScope) }() conditions.Set(s.DHCPRelay, metav1.Condition{ - Type: v1alpha1.ConfiguredCondition, - Status: metav1.ConditionFalse, + Type: v1alpha1.ConfiguredCondition, + Status: metav1.ConditionFalse, + Reason: v1alpha1.ReconcilePendingReason, + Message: "Reconciliation is in progress", }) // Ensure the DHCPRelay is owned by the Device. diff --git a/internal/controller/core/dns_controller.go b/internal/controller/core/dns_controller.go index f93c8e782..ce770bcd7 100644 --- a/internal/controller/core/dns_controller.go +++ b/internal/controller/core/dns_controller.go @@ -275,6 +275,13 @@ func (r *DNSReconciler) reconcile(ctx context.Context, s *dnsScope) (reterr erro conditions.RecomputeReady(s.DNS) }() + conditions.Set(s.DNS, metav1.Condition{ + Type: v1alpha1.ConfiguredCondition, + Status: metav1.ConditionFalse, + Reason: v1alpha1.ReconcilePendingReason, + Message: "Reconciliation is in progress", + }) + // Ensure the DNS is owned by the Device. if !controllerutil.HasControllerReference(s.DNS) { if err := controllerutil.SetOwnerReference(s.Device, s.DNS, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { diff --git a/internal/controller/core/ethernetsegment_controller.go b/internal/controller/core/ethernetsegment_controller.go index 8f0a3ee21..11b7c2c2c 100644 --- a/internal/controller/core/ethernetsegment_controller.go +++ b/internal/controller/core/ethernetsegment_controller.go @@ -313,6 +313,13 @@ func (r *EthernetSegmentReconciler) reconcile(ctx context.Context, s *ethernetSe conditions.RecomputeReady(s.EthernetSegment) }() + conditions.Set(s.EthernetSegment, metav1.Condition{ + Type: v1alpha1.ConfiguredCondition, + Status: metav1.ConditionFalse, + Reason: v1alpha1.ReconcilePendingReason, + Message: "Reconciliation is in progress", + }) + intf, err := r.reconcileInterface(ctx, s) if err != nil { return err diff --git a/internal/controller/core/evpninstance_controller.go b/internal/controller/core/evpninstance_controller.go index 7e79c22b3..318bed99d 100644 --- a/internal/controller/core/evpninstance_controller.go +++ b/internal/controller/core/evpninstance_controller.go @@ -330,6 +330,13 @@ func (r *EVPNInstanceReconciler) reconcile(ctx context.Context, s *eviScope) (re conditions.RecomputeReady(s.EVPNInstance) }() + conditions.Set(s.EVPNInstance, metav1.Condition{ + Type: v1alpha1.ConfiguredCondition, + Status: metav1.ConditionFalse, + Reason: v1alpha1.ReconcilePendingReason, + Message: "Reconciliation is in progress", + }) + // Ensure the EVPNInstance is owned by the Device. if !controllerutil.HasControllerReference(s.EVPNInstance) { if err := controllerutil.SetOwnerReference(s.Device, s.EVPNInstance, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { diff --git a/internal/controller/core/isis_controller.go b/internal/controller/core/isis_controller.go index d88a3b56f..d4cfb2c53 100644 --- a/internal/controller/core/isis_controller.go +++ b/internal/controller/core/isis_controller.go @@ -294,6 +294,13 @@ func (r *ISISReconciler) reconcile(ctx context.Context, s *isisScope) (reterr er conditions.RecomputeReady(s.ISIS) }() + conditions.Set(s.ISIS, metav1.Condition{ + Type: v1alpha1.ConfiguredCondition, + Status: metav1.ConditionFalse, + Reason: v1alpha1.ReconcilePendingReason, + Message: "Reconciliation is in progress", + }) + // Ensure the ISIS is owned by the Device. if !controllerutil.HasControllerReference(s.ISIS) { if err := controllerutil.SetOwnerReference(s.Device, s.ISIS, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { diff --git a/internal/controller/core/lldp_controller.go b/internal/controller/core/lldp_controller.go index 4a4471b45..e3e1a1d01 100644 --- a/internal/controller/core/lldp_controller.go +++ b/internal/controller/core/lldp_controller.go @@ -165,7 +165,7 @@ func (r *LLDPReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctr } orig := obj.DeepCopy() - if conditions.InitializeConditions(obj, v1alpha1.ReadyCondition) { + if conditions.InitializeConditions(obj, v1alpha1.ReadyCondition, v1alpha1.ConfiguredCondition) { log.V(1).Info("Initializing status conditions") return ctrl.Result{}, r.Status().Update(ctx, obj) } @@ -222,6 +222,13 @@ func (r *LLDPReconciler) reconcile(ctx context.Context, s *lldpScope) (reterr er conditions.RecomputeReady(s.LLDP) }() + conditions.Set(s.LLDP, metav1.Condition{ + Type: v1alpha1.ConfiguredCondition, + Status: metav1.ConditionFalse, + Reason: v1alpha1.ReconcilePendingReason, + Message: "Reconciliation is in progress", + }) + if err := r.validateUniqueLLDPPerDevice(ctx, s); err != nil { return err } diff --git a/internal/controller/core/managementaccess_controller.go b/internal/controller/core/managementaccess_controller.go index 5fdcbc7c5..4bad0e62d 100644 --- a/internal/controller/core/managementaccess_controller.go +++ b/internal/controller/core/managementaccess_controller.go @@ -275,6 +275,13 @@ func (r *ManagementAccessReconciler) reconcile(ctx context.Context, s *managemen conditions.RecomputeReady(s.ManagementAccess) }() + conditions.Set(s.ManagementAccess, metav1.Condition{ + Type: v1alpha1.ConfiguredCondition, + Status: metav1.ConditionFalse, + Reason: v1alpha1.ReconcilePendingReason, + Message: "Reconciliation is in progress", + }) + // Ensure the ManagementAccess is owned by the Device. if !controllerutil.HasControllerReference(s.ManagementAccess) { if err := controllerutil.SetOwnerReference(s.Device, s.ManagementAccess, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { diff --git a/internal/controller/core/ntp_controller.go b/internal/controller/core/ntp_controller.go index 6a1988b33..3c2f89636 100644 --- a/internal/controller/core/ntp_controller.go +++ b/internal/controller/core/ntp_controller.go @@ -275,6 +275,13 @@ func (r *NTPReconciler) reconcile(ctx context.Context, s *ntpScope) (reterr erro conditions.RecomputeReady(s.NTP) }() + conditions.Set(s.NTP, metav1.Condition{ + Type: v1alpha1.ConfiguredCondition, + Status: metav1.ConditionFalse, + Reason: v1alpha1.ReconcilePendingReason, + Message: "Reconciliation is in progress", + }) + // Ensure the NTP is owned by the Device. if !controllerutil.HasControllerReference(s.NTP) { if err := controllerutil.SetOwnerReference(s.Device, s.NTP, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { diff --git a/internal/controller/core/nve_controller.go b/internal/controller/core/nve_controller.go index bb032f748..64e7990b5 100644 --- a/internal/controller/core/nve_controller.go +++ b/internal/controller/core/nve_controller.go @@ -173,7 +173,7 @@ func (r *NetworkVirtualizationEdgeReconciler) Reconcile(ctx context.Context, req } orig := obj.DeepCopy() - if conditions.InitializeConditions(obj, v1alpha1.ReadyCondition) { + if conditions.InitializeConditions(obj, v1alpha1.ReadyCondition, v1alpha1.ConfiguredCondition) { log.V(1).Info("Initializing status conditions") return ctrl.Result{}, r.Status().Update(ctx, obj) } @@ -228,6 +228,13 @@ func (r *NetworkVirtualizationEdgeReconciler) reconcile(ctx context.Context, s * conditions.RecomputeReady(s.NVE) }() + conditions.Set(s.NVE, metav1.Condition{ + Type: v1alpha1.ConfiguredCondition, + Status: metav1.ConditionFalse, + Reason: v1alpha1.ReconcilePendingReason, + Message: "Reconciliation is in progress", + }) + if err := r.validateUniqueNVEPerDevice(ctx, s); err != nil { return err } diff --git a/internal/controller/core/ospf_controller.go b/internal/controller/core/ospf_controller.go index 2e69c38fe..8034d5949 100644 --- a/internal/controller/core/ospf_controller.go +++ b/internal/controller/core/ospf_controller.go @@ -311,6 +311,13 @@ func (r *OSPFReconciler) reconcile(ctx context.Context, s *ospfScope) (reterr er conditions.RecomputeReady(s.OSPF) }() + conditions.Set(s.OSPF, metav1.Condition{ + Type: v1alpha1.ConfiguredCondition, + Status: metav1.ConditionFalse, + Reason: v1alpha1.ReconcilePendingReason, + Message: "Reconciliation is in progress", + }) + var interfaces []provider.OSPFInterface for _, ref := range s.OSPF.Spec.InterfaceRefs { intf := new(v1alpha1.Interface) diff --git a/internal/controller/core/pim_controller.go b/internal/controller/core/pim_controller.go index e2eab9087..89a36f95d 100644 --- a/internal/controller/core/pim_controller.go +++ b/internal/controller/core/pim_controller.go @@ -294,6 +294,13 @@ func (r *PIMReconciler) reconcile(ctx context.Context, s *pimScope) (reterr erro conditions.RecomputeReady(s.PIM) }() + conditions.Set(s.PIM, metav1.Condition{ + Type: v1alpha1.ConfiguredCondition, + Status: metav1.ConditionFalse, + Reason: v1alpha1.ReconcilePendingReason, + Message: "Reconciliation is in progress", + }) + // Ensure the PIM is owned by the Device. if !controllerutil.HasControllerReference(s.PIM) { if err := controllerutil.SetOwnerReference(s.Device, s.PIM, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { diff --git a/internal/controller/core/prefixset_controller.go b/internal/controller/core/prefixset_controller.go index 60357971a..2785b0249 100644 --- a/internal/controller/core/prefixset_controller.go +++ b/internal/controller/core/prefixset_controller.go @@ -275,6 +275,13 @@ func (r *PrefixSetReconciler) reconcile(ctx context.Context, s *prefixSetScope) conditions.RecomputeReady(s.PrefixSet) }() + conditions.Set(s.PrefixSet, metav1.Condition{ + Type: v1alpha1.ConfiguredCondition, + Status: metav1.ConditionFalse, + Reason: v1alpha1.ReconcilePendingReason, + Message: "Reconciliation is in progress", + }) + // Ensure the PrefixSet is owned by the Device. if !controllerutil.HasControllerReference(s.PrefixSet) { if err := controllerutil.SetOwnerReference(s.Device, s.PrefixSet, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { diff --git a/internal/controller/core/routingpolicy_controller.go b/internal/controller/core/routingpolicy_controller.go index 0b60853c5..89c4f1cfc 100644 --- a/internal/controller/core/routingpolicy_controller.go +++ b/internal/controller/core/routingpolicy_controller.go @@ -305,8 +305,10 @@ func (r *RoutingPolicyReconciler) reconcile(ctx context.Context, s *routingPolic }() conditions.Set(s.RoutingPolicy, metav1.Condition{ - Type: v1alpha1.ConfiguredCondition, - Status: metav1.ConditionFalse, + Type: v1alpha1.ConfiguredCondition, + Status: metav1.ConditionFalse, + Reason: v1alpha1.ReconcilePendingReason, + Message: "Reconciliation is in progress", }) // Ensure the RoutingPolicy is owned by the Device. diff --git a/internal/controller/core/snmp_controller.go b/internal/controller/core/snmp_controller.go index e753aa782..d14a8dcb6 100644 --- a/internal/controller/core/snmp_controller.go +++ b/internal/controller/core/snmp_controller.go @@ -275,6 +275,13 @@ func (r *SNMPReconciler) reconcile(ctx context.Context, s *snmpScope) (reterr er conditions.RecomputeReady(s.SNMP) }() + conditions.Set(s.SNMP, metav1.Condition{ + Type: v1alpha1.ConfiguredCondition, + Status: metav1.ConditionFalse, + Reason: v1alpha1.ReconcilePendingReason, + Message: "Reconciliation is in progress", + }) + // Ensure the SNMP is owned by the Device. if !controllerutil.HasControllerReference(s.SNMP) { if err := controllerutil.SetOwnerReference(s.Device, s.SNMP, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { diff --git a/internal/controller/core/suite_test.go b/internal/controller/core/suite_test.go index f3e3a679a..c9147b73a 100644 --- a/internal/controller/core/suite_test.go +++ b/internal/controller/core/suite_test.go @@ -90,9 +90,10 @@ var _ = BeforeSuite(func() { Expect(cfg).NotTo(BeNil()) k8sManager, err = ctrl.NewManager(cfg, ctrl.Options{ - Scheme: scheme.Scheme, - Logger: GinkgoLogr, - Metrics: metricsserver.Options{BindAddress: "0"}, + Scheme: scheme.Scheme, + Logger: GinkgoLogr, + Metrics: metricsserver.Options{BindAddress: "0"}, + HealthProbeBindAddress: "0", }) Expect(err).ToNot(HaveOccurred()) diff --git a/internal/controller/core/syslog_controller.go b/internal/controller/core/syslog_controller.go index edf864d9d..bd82a9c89 100644 --- a/internal/controller/core/syslog_controller.go +++ b/internal/controller/core/syslog_controller.go @@ -275,6 +275,13 @@ func (r *SyslogReconciler) reconcile(ctx context.Context, s *syslogScope) (reter conditions.RecomputeReady(s.Syslog) }() + conditions.Set(s.Syslog, metav1.Condition{ + Type: v1alpha1.ConfiguredCondition, + Status: metav1.ConditionFalse, + Reason: v1alpha1.ReconcilePendingReason, + Message: "Reconciliation is in progress", + }) + // Ensure the Syslog is owned by the Device. if !controllerutil.HasControllerReference(s.Syslog) { if err := controllerutil.SetOwnerReference(s.Device, s.Syslog, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { diff --git a/internal/controller/core/user_controller.go b/internal/controller/core/user_controller.go index 7ac85b211..e893bef53 100644 --- a/internal/controller/core/user_controller.go +++ b/internal/controller/core/user_controller.go @@ -284,6 +284,13 @@ func (r *UserReconciler) reconcile(ctx context.Context, s *userScope) (reterr er conditions.RecomputeReady(s.User) }() + conditions.Set(s.User, metav1.Condition{ + Type: v1alpha1.ConfiguredCondition, + Status: metav1.ConditionFalse, + Reason: v1alpha1.ReconcilePendingReason, + Message: "Reconciliation is in progress", + }) + // Ensure the User is owned by the Device. if !controllerutil.HasControllerReference(s.User) { if err := controllerutil.SetOwnerReference(s.Device, s.User, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { diff --git a/internal/controller/core/vlan_controller.go b/internal/controller/core/vlan_controller.go index ef5c7b61b..eefa262fc 100644 --- a/internal/controller/core/vlan_controller.go +++ b/internal/controller/core/vlan_controller.go @@ -291,8 +291,10 @@ func (r *VLANReconciler) reconcile(ctx context.Context, s *vlanScope) (reterr er }() conditions.Set(s.VLAN, metav1.Condition{ - Type: v1alpha1.ConfiguredCondition, - Status: metav1.ConditionFalse, + Type: v1alpha1.ConfiguredCondition, + Status: metav1.ConditionFalse, + Reason: v1alpha1.ReconcilePendingReason, + Message: "Reconciliation is in progress", }) if err := s.Provider.Connect(ctx, s.Connection); err != nil { diff --git a/internal/controller/core/vrf_controller.go b/internal/controller/core/vrf_controller.go index 8eebf6b31..77b7af75b 100644 --- a/internal/controller/core/vrf_controller.go +++ b/internal/controller/core/vrf_controller.go @@ -224,8 +224,10 @@ func (r *VRFReconciler) reconcile(ctx context.Context, s *vrfScope) (reterr erro }() conditions.Set(s.VRF, metav1.Condition{ - Type: v1alpha1.ConfiguredCondition, - Status: metav1.ConditionFalse, + Type: v1alpha1.ConfiguredCondition, + Status: metav1.ConditionFalse, + Reason: v1alpha1.ReconcilePendingReason, + Message: "Reconciliation is in progress", }) // Ensure the VRF is owned by the Device. From 147a9776a835dddd59d66cf478d7a112f9472225 Mon Sep 17 00:00:00 2001 From: Adam Trizuljak Date: Mon, 3 Aug 2026 13:13:53 +0200 Subject: [PATCH 06/12] Fix bad merge Signed-off-by: Adam Trizuljak --- internal/controller/core/suite_test.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/internal/controller/core/suite_test.go b/internal/controller/core/suite_test.go index c9147b73a..d21908d8b 100644 --- a/internal/controller/core/suite_test.go +++ b/internal/controller/core/suite_test.go @@ -344,13 +344,12 @@ var _ = BeforeSuite(func() { }).SetupWithManager(ctx, k8sManager) Expect(err).NotTo(HaveOccurred()) - err = (&EthernetSegmentReconciler{ - Client: k8sManager.GetClient(), - Scheme: k8sManager.GetScheme(), - Recorder: recorder, - Provider: prov, - Locker: testLocker, - RequeueInterval: time.Second, + err = (&ConfigBackupReconciler{ + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + Recorder: recorder, + Provider: prov, + Locker: testLocker, }).SetupWithManager(ctx, k8sManager) Expect(err).NotTo(HaveOccurred()) From 77fb7188fdef9992f3102e97eed5b390853d85c3 Mon Sep 17 00:00:00 2001 From: Adam Trizuljak Date: Wed, 5 Aug 2026 15:13:17 +0200 Subject: [PATCH 07/12] Note Signed-off-by: Adam Trizuljak --- internal/controller/core/suite_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/controller/core/suite_test.go b/internal/controller/core/suite_test.go index d21908d8b..55c78977d 100644 --- a/internal/controller/core/suite_test.go +++ b/internal/controller/core/suite_test.go @@ -93,7 +93,7 @@ var _ = BeforeSuite(func() { Scheme: scheme.Scheme, Logger: GinkgoLogr, Metrics: metricsserver.Options{BindAddress: "0"}, - HealthProbeBindAddress: "0", + HealthProbeBindAddress: "0", // This enables running multiple instances of the test suite in parallel }) Expect(err).ToNot(HaveOccurred()) From 8a34a0a617657ddbf8859d56f3cc510be12abbb3 Mon Sep 17 00:00:00 2001 From: Adam Trizuljak Date: Tue, 11 Aug 2026 09:18:51 +0200 Subject: [PATCH 08/12] Don't reset Configured condition to False This would always update the LastTransitionedAt timestamp and force another reconciliation Signed-off-by: Adam Trizuljak --- internal/controller/cisco/nx/bordergateway_controller.go | 7 ------- internal/controller/cisco/nx/system_controller.go | 7 ------- internal/controller/cisco/nx/vpcdomain_controller.go | 7 ------- internal/controller/core/aaa_controller.go | 7 ------- internal/controller/core/acl_controller.go | 7 ------- internal/controller/core/banner_controller.go | 7 ------- internal/controller/core/bgp_controller.go | 7 ------- internal/controller/core/bgp_peer_controller.go | 7 ------- internal/controller/core/certificate_controller.go | 7 ------- internal/controller/core/dhcprelay_controller.go | 7 ------- internal/controller/core/dns_controller.go | 7 ------- internal/controller/core/ethernetsegment_controller.go | 7 ------- internal/controller/core/evpninstance_controller.go | 7 ------- internal/controller/core/isis_controller.go | 7 ------- internal/controller/core/lldp_controller.go | 7 ------- internal/controller/core/managementaccess_controller.go | 7 ------- internal/controller/core/ntp_controller.go | 7 ------- internal/controller/core/nve_controller.go | 7 ------- internal/controller/core/ospf_controller.go | 7 ------- internal/controller/core/pim_controller.go | 7 ------- internal/controller/core/prefixset_controller.go | 7 ------- internal/controller/core/routingpolicy_controller.go | 7 ------- internal/controller/core/snmp_controller.go | 7 ------- internal/controller/core/syslog_controller.go | 7 ------- internal/controller/core/user_controller.go | 7 ------- internal/controller/core/vlan_controller.go | 7 ------- internal/controller/core/vrf_controller.go | 7 ------- 27 files changed, 189 deletions(-) diff --git a/internal/controller/cisco/nx/bordergateway_controller.go b/internal/controller/cisco/nx/bordergateway_controller.go index 804bf3a67..44d77dae1 100644 --- a/internal/controller/cisco/nx/bordergateway_controller.go +++ b/internal/controller/cisco/nx/bordergateway_controller.go @@ -331,13 +331,6 @@ func (r *BorderGatewayReconciler) reconcile(ctx context.Context, s *borderGatewa conditions.RecomputeReady(s.BorderGateway) }() - conditions.Set(s.BorderGateway, metav1.Condition{ - Type: v1alpha1.ConfiguredCondition, - Status: metav1.ConditionFalse, - Reason: v1alpha1.ReconcilePendingReason, - Message: "Reconciliation is in progress", - }) - // Ensure the BorderGateway is owned by the Device. if !controllerutil.HasControllerReference(s.BorderGateway) { if err := controllerutil.SetOwnerReference(s.Device, s.BorderGateway, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { diff --git a/internal/controller/cisco/nx/system_controller.go b/internal/controller/cisco/nx/system_controller.go index 82bcc986d..6b4735027 100644 --- a/internal/controller/cisco/nx/system_controller.go +++ b/internal/controller/cisco/nx/system_controller.go @@ -251,13 +251,6 @@ func (r *SystemReconciler) reconcile(ctx context.Context, s *systemScope) (reter conditions.RecomputeReady(s.System) }() - conditions.Set(s.System, metav1.Condition{ - Type: v1alpha1.ConfiguredCondition, - Status: metav1.ConditionFalse, - Reason: v1alpha1.ReconcilePendingReason, - Message: "Reconciliation is in progress", - }) - // Ensure the System is owned by the Device. if !controllerutil.HasControllerReference(s.System) { if err := controllerutil.SetOwnerReference(s.Device, s.System, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { diff --git a/internal/controller/cisco/nx/vpcdomain_controller.go b/internal/controller/cisco/nx/vpcdomain_controller.go index ddb31d61e..a6320c5f0 100644 --- a/internal/controller/cisco/nx/vpcdomain_controller.go +++ b/internal/controller/cisco/nx/vpcdomain_controller.go @@ -297,13 +297,6 @@ func (r *VPCDomainReconciler) reconcile(ctx context.Context, s *vpcdomainScope) conditions.RecomputeReady(s.VPCDomain) }() - conditions.Set(s.VPCDomain, metav1.Condition{ - Type: v1alpha1.ConfiguredCondition, - Status: metav1.ConditionFalse, - Reason: v1alpha1.ReconcilePendingReason, - Message: "Reconciliation is in progress", - }) - peerLink, err := r.reconcilePeerLink(ctx, s) if err != nil { reterr = kerrors.NewAggregate([]error{reterr, fmt.Errorf("failed to reconcile referenced resource: %w", err)}) diff --git a/internal/controller/core/aaa_controller.go b/internal/controller/core/aaa_controller.go index 9e3e90d80..fdb866512 100644 --- a/internal/controller/core/aaa_controller.go +++ b/internal/controller/core/aaa_controller.go @@ -246,13 +246,6 @@ func (r *AAAReconciler) reconcile(ctx context.Context, s *aaaScope) (reterr erro conditions.RecomputeReady(s.AAA) }() - conditions.Set(s.AAA, metav1.Condition{ - Type: v1alpha1.ConfiguredCondition, - Status: metav1.ConditionFalse, - Reason: v1alpha1.ReconcilePendingReason, - Message: "Reconciliation is in progress", - }) - // Ensure the AAA is owned by the Device. if !controllerutil.HasControllerReference(s.AAA) { if err := controllerutil.SetOwnerReference(s.Device, s.AAA, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { diff --git a/internal/controller/core/acl_controller.go b/internal/controller/core/acl_controller.go index 25075dfb1..b3a39c971 100644 --- a/internal/controller/core/acl_controller.go +++ b/internal/controller/core/acl_controller.go @@ -275,13 +275,6 @@ func (r *AccessControlListReconciler) reconcile(ctx context.Context, s *aclScope conditions.RecomputeReady(s.ACL) }() - conditions.Set(s.ACL, metav1.Condition{ - Type: v1alpha1.ConfiguredCondition, - Status: metav1.ConditionFalse, - Reason: v1alpha1.ReconcilePendingReason, - Message: "Reconciliation is in progress", - }) - // Ensure the AccessControlList is owned by the Device. if !controllerutil.HasControllerReference(s.ACL) { if err := controllerutil.SetOwnerReference(s.Device, s.ACL, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { diff --git a/internal/controller/core/banner_controller.go b/internal/controller/core/banner_controller.go index 5e2936c16..9aaf4f770 100644 --- a/internal/controller/core/banner_controller.go +++ b/internal/controller/core/banner_controller.go @@ -291,13 +291,6 @@ func (r *BannerReconciler) reconcile(ctx context.Context, s *bannerScope) (reter conditions.RecomputeReady(s.Banner) }() - conditions.Set(s.Banner, metav1.Condition{ - Type: v1alpha1.ConfiguredCondition, - Status: metav1.ConditionFalse, - Reason: v1alpha1.ReconcilePendingReason, - Message: "Reconciliation is in progress", - }) - // Ensure the Banner is owned by the Device. if !controllerutil.HasControllerReference(s.Banner) { if err := controllerutil.SetOwnerReference(s.Device, s.Banner, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { diff --git a/internal/controller/core/bgp_controller.go b/internal/controller/core/bgp_controller.go index 288ae1cd2..7e9a9893e 100644 --- a/internal/controller/core/bgp_controller.go +++ b/internal/controller/core/bgp_controller.go @@ -352,13 +352,6 @@ func (r *BGPReconciler) reconcile(ctx context.Context, s *bgpScope) (reterr erro conditions.RecomputeReady(s.BGP) }() - conditions.Set(s.BGP, metav1.Condition{ - Type: v1alpha1.ConfiguredCondition, - Status: metav1.ConditionFalse, - Reason: v1alpha1.ReconcilePendingReason, - Message: "Reconciliation is in progress", - }) - // Ensure the BGP is owned by the Device. if !controllerutil.HasControllerReference(s.BGP) { if err := controllerutil.SetOwnerReference(s.Device, s.BGP, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { diff --git a/internal/controller/core/bgp_peer_controller.go b/internal/controller/core/bgp_peer_controller.go index b49823567..201ca4fb4 100644 --- a/internal/controller/core/bgp_peer_controller.go +++ b/internal/controller/core/bgp_peer_controller.go @@ -382,13 +382,6 @@ func (r *BGPPeerReconciler) reconcile(ctx context.Context, s *bgpPeerScope) (ret conditions.RecomputeReady(s.BGPPeer) }() - conditions.Set(s.BGPPeer, metav1.Condition{ - Type: v1alpha1.ConfiguredCondition, - Status: metav1.ConditionFalse, - Reason: v1alpha1.ReconcilePendingReason, - Message: "Reconciliation is in progress", - }) - bgp, err := r.reconcileBGP(ctx, s.BGPPeer, s.Device) if err != nil { return err diff --git a/internal/controller/core/certificate_controller.go b/internal/controller/core/certificate_controller.go index 5166b820c..ff88d4257 100644 --- a/internal/controller/core/certificate_controller.go +++ b/internal/controller/core/certificate_controller.go @@ -284,13 +284,6 @@ func (r *CertificateReconciler) reconcile(ctx context.Context, s *certificateSco conditions.RecomputeReady(s.Certificate) }() - conditions.Set(s.Certificate, metav1.Condition{ - Type: v1alpha1.ConfiguredCondition, - Status: metav1.ConditionFalse, - Reason: v1alpha1.ReconcilePendingReason, - Message: "Reconciliation is in progress", - }) - // Ensure the Certificate is owned by the Device. if !controllerutil.HasControllerReference(s.Certificate) { if err := controllerutil.SetOwnerReference(s.Device, s.Certificate, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { diff --git a/internal/controller/core/dhcprelay_controller.go b/internal/controller/core/dhcprelay_controller.go index 29bbb9f64..7f6e7488c 100644 --- a/internal/controller/core/dhcprelay_controller.go +++ b/internal/controller/core/dhcprelay_controller.go @@ -227,13 +227,6 @@ func (r *DHCPRelayReconciler) reconcile(ctx context.Context, s *dhcprelayScope) conditions.RecomputeReady(s.DHCPRelay) }() - conditions.Set(s.DHCPRelay, metav1.Condition{ - Type: v1alpha1.ConfiguredCondition, - Status: metav1.ConditionFalse, - Reason: v1alpha1.ReconcilePendingReason, - Message: "Reconciliation is in progress", - }) - // Ensure the DHCPRelay is owned by the Device. if !controllerutil.HasControllerReference(s.DHCPRelay) { if err := controllerutil.SetOwnerReference(s.Device, s.DHCPRelay, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { diff --git a/internal/controller/core/dns_controller.go b/internal/controller/core/dns_controller.go index ce770bcd7..f93c8e782 100644 --- a/internal/controller/core/dns_controller.go +++ b/internal/controller/core/dns_controller.go @@ -275,13 +275,6 @@ func (r *DNSReconciler) reconcile(ctx context.Context, s *dnsScope) (reterr erro conditions.RecomputeReady(s.DNS) }() - conditions.Set(s.DNS, metav1.Condition{ - Type: v1alpha1.ConfiguredCondition, - Status: metav1.ConditionFalse, - Reason: v1alpha1.ReconcilePendingReason, - Message: "Reconciliation is in progress", - }) - // Ensure the DNS is owned by the Device. if !controllerutil.HasControllerReference(s.DNS) { if err := controllerutil.SetOwnerReference(s.Device, s.DNS, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { diff --git a/internal/controller/core/ethernetsegment_controller.go b/internal/controller/core/ethernetsegment_controller.go index 11b7c2c2c..8f0a3ee21 100644 --- a/internal/controller/core/ethernetsegment_controller.go +++ b/internal/controller/core/ethernetsegment_controller.go @@ -313,13 +313,6 @@ func (r *EthernetSegmentReconciler) reconcile(ctx context.Context, s *ethernetSe conditions.RecomputeReady(s.EthernetSegment) }() - conditions.Set(s.EthernetSegment, metav1.Condition{ - Type: v1alpha1.ConfiguredCondition, - Status: metav1.ConditionFalse, - Reason: v1alpha1.ReconcilePendingReason, - Message: "Reconciliation is in progress", - }) - intf, err := r.reconcileInterface(ctx, s) if err != nil { return err diff --git a/internal/controller/core/evpninstance_controller.go b/internal/controller/core/evpninstance_controller.go index 318bed99d..7e79c22b3 100644 --- a/internal/controller/core/evpninstance_controller.go +++ b/internal/controller/core/evpninstance_controller.go @@ -330,13 +330,6 @@ func (r *EVPNInstanceReconciler) reconcile(ctx context.Context, s *eviScope) (re conditions.RecomputeReady(s.EVPNInstance) }() - conditions.Set(s.EVPNInstance, metav1.Condition{ - Type: v1alpha1.ConfiguredCondition, - Status: metav1.ConditionFalse, - Reason: v1alpha1.ReconcilePendingReason, - Message: "Reconciliation is in progress", - }) - // Ensure the EVPNInstance is owned by the Device. if !controllerutil.HasControllerReference(s.EVPNInstance) { if err := controllerutil.SetOwnerReference(s.Device, s.EVPNInstance, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { diff --git a/internal/controller/core/isis_controller.go b/internal/controller/core/isis_controller.go index d4cfb2c53..d88a3b56f 100644 --- a/internal/controller/core/isis_controller.go +++ b/internal/controller/core/isis_controller.go @@ -294,13 +294,6 @@ func (r *ISISReconciler) reconcile(ctx context.Context, s *isisScope) (reterr er conditions.RecomputeReady(s.ISIS) }() - conditions.Set(s.ISIS, metav1.Condition{ - Type: v1alpha1.ConfiguredCondition, - Status: metav1.ConditionFalse, - Reason: v1alpha1.ReconcilePendingReason, - Message: "Reconciliation is in progress", - }) - // Ensure the ISIS is owned by the Device. if !controllerutil.HasControllerReference(s.ISIS) { if err := controllerutil.SetOwnerReference(s.Device, s.ISIS, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { diff --git a/internal/controller/core/lldp_controller.go b/internal/controller/core/lldp_controller.go index e3e1a1d01..9fab42a8b 100644 --- a/internal/controller/core/lldp_controller.go +++ b/internal/controller/core/lldp_controller.go @@ -222,13 +222,6 @@ func (r *LLDPReconciler) reconcile(ctx context.Context, s *lldpScope) (reterr er conditions.RecomputeReady(s.LLDP) }() - conditions.Set(s.LLDP, metav1.Condition{ - Type: v1alpha1.ConfiguredCondition, - Status: metav1.ConditionFalse, - Reason: v1alpha1.ReconcilePendingReason, - Message: "Reconciliation is in progress", - }) - if err := r.validateUniqueLLDPPerDevice(ctx, s); err != nil { return err } diff --git a/internal/controller/core/managementaccess_controller.go b/internal/controller/core/managementaccess_controller.go index 4bad0e62d..5fdcbc7c5 100644 --- a/internal/controller/core/managementaccess_controller.go +++ b/internal/controller/core/managementaccess_controller.go @@ -275,13 +275,6 @@ func (r *ManagementAccessReconciler) reconcile(ctx context.Context, s *managemen conditions.RecomputeReady(s.ManagementAccess) }() - conditions.Set(s.ManagementAccess, metav1.Condition{ - Type: v1alpha1.ConfiguredCondition, - Status: metav1.ConditionFalse, - Reason: v1alpha1.ReconcilePendingReason, - Message: "Reconciliation is in progress", - }) - // Ensure the ManagementAccess is owned by the Device. if !controllerutil.HasControllerReference(s.ManagementAccess) { if err := controllerutil.SetOwnerReference(s.Device, s.ManagementAccess, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { diff --git a/internal/controller/core/ntp_controller.go b/internal/controller/core/ntp_controller.go index 3c2f89636..6a1988b33 100644 --- a/internal/controller/core/ntp_controller.go +++ b/internal/controller/core/ntp_controller.go @@ -275,13 +275,6 @@ func (r *NTPReconciler) reconcile(ctx context.Context, s *ntpScope) (reterr erro conditions.RecomputeReady(s.NTP) }() - conditions.Set(s.NTP, metav1.Condition{ - Type: v1alpha1.ConfiguredCondition, - Status: metav1.ConditionFalse, - Reason: v1alpha1.ReconcilePendingReason, - Message: "Reconciliation is in progress", - }) - // Ensure the NTP is owned by the Device. if !controllerutil.HasControllerReference(s.NTP) { if err := controllerutil.SetOwnerReference(s.Device, s.NTP, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { diff --git a/internal/controller/core/nve_controller.go b/internal/controller/core/nve_controller.go index 64e7990b5..0a7feb878 100644 --- a/internal/controller/core/nve_controller.go +++ b/internal/controller/core/nve_controller.go @@ -228,13 +228,6 @@ func (r *NetworkVirtualizationEdgeReconciler) reconcile(ctx context.Context, s * conditions.RecomputeReady(s.NVE) }() - conditions.Set(s.NVE, metav1.Condition{ - Type: v1alpha1.ConfiguredCondition, - Status: metav1.ConditionFalse, - Reason: v1alpha1.ReconcilePendingReason, - Message: "Reconciliation is in progress", - }) - if err := r.validateUniqueNVEPerDevice(ctx, s); err != nil { return err } diff --git a/internal/controller/core/ospf_controller.go b/internal/controller/core/ospf_controller.go index 8034d5949..2e69c38fe 100644 --- a/internal/controller/core/ospf_controller.go +++ b/internal/controller/core/ospf_controller.go @@ -311,13 +311,6 @@ func (r *OSPFReconciler) reconcile(ctx context.Context, s *ospfScope) (reterr er conditions.RecomputeReady(s.OSPF) }() - conditions.Set(s.OSPF, metav1.Condition{ - Type: v1alpha1.ConfiguredCondition, - Status: metav1.ConditionFalse, - Reason: v1alpha1.ReconcilePendingReason, - Message: "Reconciliation is in progress", - }) - var interfaces []provider.OSPFInterface for _, ref := range s.OSPF.Spec.InterfaceRefs { intf := new(v1alpha1.Interface) diff --git a/internal/controller/core/pim_controller.go b/internal/controller/core/pim_controller.go index 89a36f95d..e2eab9087 100644 --- a/internal/controller/core/pim_controller.go +++ b/internal/controller/core/pim_controller.go @@ -294,13 +294,6 @@ func (r *PIMReconciler) reconcile(ctx context.Context, s *pimScope) (reterr erro conditions.RecomputeReady(s.PIM) }() - conditions.Set(s.PIM, metav1.Condition{ - Type: v1alpha1.ConfiguredCondition, - Status: metav1.ConditionFalse, - Reason: v1alpha1.ReconcilePendingReason, - Message: "Reconciliation is in progress", - }) - // Ensure the PIM is owned by the Device. if !controllerutil.HasControllerReference(s.PIM) { if err := controllerutil.SetOwnerReference(s.Device, s.PIM, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { diff --git a/internal/controller/core/prefixset_controller.go b/internal/controller/core/prefixset_controller.go index 2785b0249..60357971a 100644 --- a/internal/controller/core/prefixset_controller.go +++ b/internal/controller/core/prefixset_controller.go @@ -275,13 +275,6 @@ func (r *PrefixSetReconciler) reconcile(ctx context.Context, s *prefixSetScope) conditions.RecomputeReady(s.PrefixSet) }() - conditions.Set(s.PrefixSet, metav1.Condition{ - Type: v1alpha1.ConfiguredCondition, - Status: metav1.ConditionFalse, - Reason: v1alpha1.ReconcilePendingReason, - Message: "Reconciliation is in progress", - }) - // Ensure the PrefixSet is owned by the Device. if !controllerutil.HasControllerReference(s.PrefixSet) { if err := controllerutil.SetOwnerReference(s.Device, s.PrefixSet, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { diff --git a/internal/controller/core/routingpolicy_controller.go b/internal/controller/core/routingpolicy_controller.go index 89c4f1cfc..7a982023f 100644 --- a/internal/controller/core/routingpolicy_controller.go +++ b/internal/controller/core/routingpolicy_controller.go @@ -304,13 +304,6 @@ func (r *RoutingPolicyReconciler) reconcile(ctx context.Context, s *routingPolic conditions.RecomputeReady(s.RoutingPolicy) }() - conditions.Set(s.RoutingPolicy, metav1.Condition{ - Type: v1alpha1.ConfiguredCondition, - Status: metav1.ConditionFalse, - Reason: v1alpha1.ReconcilePendingReason, - Message: "Reconciliation is in progress", - }) - // Ensure the RoutingPolicy is owned by the Device. if !controllerutil.HasControllerReference(s.RoutingPolicy) { if err := controllerutil.SetOwnerReference(s.Device, s.RoutingPolicy, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { diff --git a/internal/controller/core/snmp_controller.go b/internal/controller/core/snmp_controller.go index d14a8dcb6..e753aa782 100644 --- a/internal/controller/core/snmp_controller.go +++ b/internal/controller/core/snmp_controller.go @@ -275,13 +275,6 @@ func (r *SNMPReconciler) reconcile(ctx context.Context, s *snmpScope) (reterr er conditions.RecomputeReady(s.SNMP) }() - conditions.Set(s.SNMP, metav1.Condition{ - Type: v1alpha1.ConfiguredCondition, - Status: metav1.ConditionFalse, - Reason: v1alpha1.ReconcilePendingReason, - Message: "Reconciliation is in progress", - }) - // Ensure the SNMP is owned by the Device. if !controllerutil.HasControllerReference(s.SNMP) { if err := controllerutil.SetOwnerReference(s.Device, s.SNMP, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { diff --git a/internal/controller/core/syslog_controller.go b/internal/controller/core/syslog_controller.go index bd82a9c89..edf864d9d 100644 --- a/internal/controller/core/syslog_controller.go +++ b/internal/controller/core/syslog_controller.go @@ -275,13 +275,6 @@ func (r *SyslogReconciler) reconcile(ctx context.Context, s *syslogScope) (reter conditions.RecomputeReady(s.Syslog) }() - conditions.Set(s.Syslog, metav1.Condition{ - Type: v1alpha1.ConfiguredCondition, - Status: metav1.ConditionFalse, - Reason: v1alpha1.ReconcilePendingReason, - Message: "Reconciliation is in progress", - }) - // Ensure the Syslog is owned by the Device. if !controllerutil.HasControllerReference(s.Syslog) { if err := controllerutil.SetOwnerReference(s.Device, s.Syslog, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { diff --git a/internal/controller/core/user_controller.go b/internal/controller/core/user_controller.go index e893bef53..7ac85b211 100644 --- a/internal/controller/core/user_controller.go +++ b/internal/controller/core/user_controller.go @@ -284,13 +284,6 @@ func (r *UserReconciler) reconcile(ctx context.Context, s *userScope) (reterr er conditions.RecomputeReady(s.User) }() - conditions.Set(s.User, metav1.Condition{ - Type: v1alpha1.ConfiguredCondition, - Status: metav1.ConditionFalse, - Reason: v1alpha1.ReconcilePendingReason, - Message: "Reconciliation is in progress", - }) - // Ensure the User is owned by the Device. if !controllerutil.HasControllerReference(s.User) { if err := controllerutil.SetOwnerReference(s.Device, s.User, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { diff --git a/internal/controller/core/vlan_controller.go b/internal/controller/core/vlan_controller.go index eefa262fc..c86866302 100644 --- a/internal/controller/core/vlan_controller.go +++ b/internal/controller/core/vlan_controller.go @@ -290,13 +290,6 @@ func (r *VLANReconciler) reconcile(ctx context.Context, s *vlanScope) (reterr er conditions.RecomputeReady(s.VLAN) }() - conditions.Set(s.VLAN, metav1.Condition{ - Type: v1alpha1.ConfiguredCondition, - Status: metav1.ConditionFalse, - Reason: v1alpha1.ReconcilePendingReason, - Message: "Reconciliation is in progress", - }) - if err := s.Provider.Connect(ctx, s.Connection); err != nil { return fmt.Errorf("failed to connect to provider: %w", err) } diff --git a/internal/controller/core/vrf_controller.go b/internal/controller/core/vrf_controller.go index 77b7af75b..163d0e689 100644 --- a/internal/controller/core/vrf_controller.go +++ b/internal/controller/core/vrf_controller.go @@ -223,13 +223,6 @@ func (r *VRFReconciler) reconcile(ctx context.Context, s *vrfScope) (reterr erro conditions.RecomputeReady(s.VRF) }() - conditions.Set(s.VRF, metav1.Condition{ - Type: v1alpha1.ConfiguredCondition, - Status: metav1.ConditionFalse, - Reason: v1alpha1.ReconcilePendingReason, - Message: "Reconciliation is in progress", - }) - // Ensure the VRF is owned by the Device. if !controllerutil.HasControllerReference(s.VRF) { if err := controllerutil.SetOwnerReference(s.Device, s.VRF, r.Scheme, controllerutil.WithBlockOwnerDeletion(true)); err != nil { From 5f5baf1c68858f1b6f272f72ef7da81f6d625ec5 Mon Sep 17 00:00:00 2001 From: Adam Trizuljak Date: Thu, 20 Aug 2026 09:31:38 +0200 Subject: [PATCH 09/12] Improve device creation and deletion in BGP, BGPPeer tests Wait for Device to be actually created Create a different device, let it reconcile, clean it up and its referenced interfaces Signed-off-by: Adam Trizuljak --- .../controller/core/bgp_controller_test.go | 6 +++ .../core/bgp_peer_controller_test.go | 37 ++++++++++++++++++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/internal/controller/core/bgp_controller_test.go b/internal/controller/core/bgp_controller_test.go index 1ea78b20c..34e7e0d25 100644 --- a/internal/controller/core/bgp_controller_test.go +++ b/internal/controller/core/bgp_controller_test.go @@ -33,6 +33,12 @@ var _ = Describe("BGP Controller", func() { }, } Expect(k8sClient.Create(ctx, device)).To(Succeed()) + + By("Waiting for the Device to be created") + Eventually(func(g Gomega) { + resource := &v1alpha1.Device{} + g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(device), resource)).To(Succeed()) + }).Should(Succeed()) }) AfterEach(func() { diff --git a/internal/controller/core/bgp_peer_controller_test.go b/internal/controller/core/bgp_peer_controller_test.go index cebe4eb78..9a8aacfb5 100644 --- a/internal/controller/core/bgp_peer_controller_test.go +++ b/internal/controller/core/bgp_peer_controller_test.go @@ -34,6 +34,12 @@ var _ = Describe("BGPPeer Controller", func() { }, } Expect(k8sClient.Create(ctx, device)).To(Succeed()) + + By("Waiting for the Device to be created") + Eventually(func(g Gomega) { + resource := &v1alpha1.Device{} + g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(device), resource)).To(Succeed()) + }).Should(Succeed()) }) AfterEach(func() { @@ -297,6 +303,35 @@ var _ = Describe("BGPPeer Controller", func() { }) It("Should reject local address reference to Interface on different device", func() { + By("Creating a different Device resource for testing") + differentDevice := &v1alpha1.Device{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "different-device-", + Namespace: metav1.NamespaceDefault, + }, + Spec: v1alpha1.DeviceSpec{ + Endpoint: v1alpha1.Endpoint{ + Address: "192.168.10.3:9339", + }, + }, + } + Expect(k8sClient.Create(ctx, differentDevice)).To(Succeed()) + DeferCleanup(func() { + intfList := &v1alpha1.InterfaceList{} + Expect(k8sClient.List(ctx, intfList, client.InNamespace(metav1.NamespaceDefault), client.MatchingLabels{v1alpha1.DeviceLabel: differentDevice.Name})).To(Succeed()) + for i := range intfList.Items { + Expect(client.IgnoreNotFound(k8sClient.Delete(ctx, &intfList.Items[i]))).To(Succeed()) + } + Expect(client.IgnoreNotFound(k8sClient.Delete(ctx, differentDevice))).To(Succeed()) + }) + + By("Waiting for the different Device to be in Running phase") + Eventually(func(g Gomega) { + d := &v1alpha1.Device{} + g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(differentDevice), d)).To(Succeed()) + g.Expect(d.Status.Phase).To(Equal(v1alpha1.DevicePhaseRunning)) + }).Should(Succeed()) + By("Creating a BGP resource for the Device") bgp := &v1alpha1.BGP{ ObjectMeta: metav1.ObjectMeta{ @@ -325,7 +360,7 @@ var _ = Describe("BGPPeer Controller", func() { Namespace: metav1.NamespaceDefault, }, Spec: v1alpha1.InterfaceSpec{ - DeviceRef: v1alpha1.LocalObjectReference{Name: "different-device"}, + DeviceRef: v1alpha1.LocalObjectReference{Name: differentDevice.Name}, Name: "Loopback0", AdminState: v1alpha1.AdminStateUp, Type: v1alpha1.InterfaceTypeLoopback, From e1e1a06d03d3ba3c3a08de7e1a1e8a5448cff276 Mon Sep 17 00:00:00 2001 From: Adam Trizuljak Date: Thu, 20 Aug 2026 09:36:54 +0200 Subject: [PATCH 10/12] Allow BGPPeer controller to continue reconciling when interface is not found Changed from TerminalError to regular error -> controller will retry reconciliation Watche enqueues BGPPeers for reconciliation when a referenced Interface is created or deleted Signed-off-by: Adam Trizuljak --- .../controller/core/bgp_peer_controller.go | 63 ++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/internal/controller/core/bgp_peer_controller.go b/internal/controller/core/bgp_peer_controller.go index 201ca4fb4..23e373a4e 100644 --- a/internal/controller/core/bgp_peer_controller.go +++ b/internal/controller/core/bgp_peer_controller.go @@ -47,6 +47,9 @@ const bgpPeerBGPRefIndexKey = ".spec.bgpRef.name" // referenced by BGPPeer address families. const bgpPeerRoutingPolicyRefIndexKey = ".spec.addressFamilies.routingPolicyRefs" +// bgpPeerInterfaceRefIndexKey is the field index key for BGPPeer.Spec.LocalAddress.InterfaceRef.Name. +const bgpPeerInterfaceRefIndexKey = ".spec.localAddress.interfaceRef.name" + // BGPPeerReconciler reconciles a BGPPeer object type BGPPeerReconciler struct { client.Client @@ -275,6 +278,16 @@ func (r *BGPPeerReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manag return err } + if err := mgr.GetFieldIndexer().IndexField(ctx, &v1alpha1.BGPPeer{}, bgpPeerInterfaceRefIndexKey, func(obj client.Object) []string { + o := obj.(*v1alpha1.BGPPeer) + if o.Spec.LocalAddress == nil { + return nil + } + return []string{o.Spec.LocalAddress.InterfaceRef.Name} + }); err != nil { + return err + } + bldr := ctrl.NewControllerManagedBy(mgr). For(&v1alpha1.BGPPeer{}). Named("bgppeer"). @@ -352,6 +365,20 @@ func (r *BGPPeerReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manag }, }), ). + // Watches enqueues BGPPeers when a referenced Interface is created or deleted. + // Only triggers on create and delete events since Interface names are immutable. + Watches( + &v1alpha1.Interface{}, + handler.EnqueueRequestsFromMapFunc(r.interfaceToBGPPeers), + builder.WithPredicates(predicate.Funcs{ + UpdateFunc: func(e event.UpdateEvent) bool { + return false + }, + GenericFunc: func(e event.GenericEvent) bool { + return false + }, + }), + ). Complete(r) } @@ -426,7 +453,7 @@ func (r *BGPPeerReconciler) reconcile(ctx context.Context, s *bgpPeerScope) (ret Reason: v1alpha1.InterfaceNotFoundReason, Message: fmt.Sprintf("source interface %q not found", addr.InterfaceRef.Name), }) - return reconcile.TerminalError(fmt.Errorf("source interface %q not found", addr.InterfaceRef.Name)) + return fmt.Errorf("source interface %q not found", addr.InterfaceRef.Name) } return fmt.Errorf("failed to get source interface %q: %w", addr.InterfaceRef.Name, err) } @@ -885,3 +912,37 @@ func (r *BGPPeerReconciler) routingPolicyToBGPPeers(ctx context.Context, obj cli return requests } + +// interfaceToBGPPeers is a [handler.MapFunc] to be used to enqueue requests for reconciliation +// for BGPPeers when an Interface referenced by their local address is created or deleted. +func (r *BGPPeerReconciler) interfaceToBGPPeers(ctx context.Context, obj client.Object) []ctrl.Request { + intf, ok := obj.(*v1alpha1.Interface) + if !ok { + panic(fmt.Sprintf("Expected an Interface but got a %T", obj)) + } + + log := ctrl.LoggerFrom(ctx, "Interface", klog.KObj(intf)) + + list := new(v1alpha1.BGPPeerList) + if err := r.List( + ctx, list, + client.InNamespace(intf.Namespace), + client.MatchingFields{bgpPeerInterfaceRefIndexKey: intf.Name}, + ); err != nil { + log.Error(err, "Failed to list BGPPeers") + return nil + } + + requests := make([]ctrl.Request, 0, len(list.Items)) + for _, p := range list.Items { + log.V(2).Info("Enqueuing BGPPeer for reconciliation", "BGPPeer", klog.KObj(&p)) + requests = append(requests, ctrl.Request{ + NamespacedName: types.NamespacedName{ + Name: p.Name, + Namespace: p.Namespace, + }, + }) + } + + return requests +} From 124a34b97bf77a0580780aa68fcfa2ca36ccf795 Mon Sep 17 00:00:00 2001 From: Adam Trizuljak Date: Thu, 20 Aug 2026 13:10:10 +0200 Subject: [PATCH 11/12] Fix EthernetSegment tests failing due to Interface controller lock starvation Tests 1, 5, and 6 created Aggregate Interfaces with MemberInterfaceRefs pointing to "eth1", which didn't exist as a Kubernetes resource. This caused the Interface controller to return a terminal error on every reconcile pass, cycling through 4 passes while holding the device Lease at priority 10. The EthernetSegment controller (priority 1) could never acquire the same Lease within the 60s timeout, so its finalizer was never added. Fix by creating a real Physical Interface as the member reference before each Aggregate Interface. The Interface controller can now resolve all member refs, reconciles successfully, and stops holding the lock. A "Wait for Interface configured" step is added before EthernetSegment creation to ensure the Interface controller has fully finished before the EthernetSegment controller competes for the lock. Signed-off-by: Adam Trizuljak --- .../core/ethernetsegment_controller_test.go | 79 ++++++++++++++++++- 1 file changed, 76 insertions(+), 3 deletions(-) diff --git a/internal/controller/core/ethernetsegment_controller_test.go b/internal/controller/core/ethernetsegment_controller_test.go index bff49709e..f1ba320b6 100644 --- a/internal/controller/core/ethernetsegment_controller_test.go +++ b/internal/controller/core/ethernetsegment_controller_test.go @@ -11,6 +11,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "github.com/ironcore-dev/network-operator/api/core/v1alpha1" + "github.com/ironcore-dev/network-operator/internal/conditions" ) var _ = Describe("EthernetSegment Controller", func() { @@ -65,6 +66,12 @@ var _ = Describe("EthernetSegment Controller", func() { intf.Namespace = metav1.NamespaceDefault Expect(client.IgnoreNotFound(k8sClient.Delete(ctx, intf))).To(Succeed()) + By("Cleaning up test member Interface resource") + memberIntf := &v1alpha1.Interface{} + memberIntf.Name = name + "-member" + memberIntf.Namespace = metav1.NamespaceDefault + Expect(client.IgnoreNotFound(k8sClient.Delete(ctx, memberIntf))).To(Succeed()) + By("Cleaning up the test Device resource") device := &v1alpha1.Device{} device.Name = name @@ -73,6 +80,21 @@ var _ = Describe("EthernetSegment Controller", func() { }) It("Should successfully reconcile an EthernetSegment", func() { + By("Creating a Physical member Interface") + memberIntf := &v1alpha1.Interface{ + ObjectMeta: metav1.ObjectMeta{ + Name: name + "-member", + Namespace: metav1.NamespaceDefault, + }, + Spec: v1alpha1.InterfaceSpec{ + DeviceRef: v1alpha1.LocalObjectReference{Name: name}, + Name: "eth1", + Type: v1alpha1.InterfaceTypePhysical, + AdminState: v1alpha1.AdminStateUp, + }, + } + Expect(k8sClient.Create(ctx, memberIntf)).To(Succeed()) + By("Creating an Aggregate Interface with switchport config") intf := &v1alpha1.Interface{ ObjectMeta: metav1.ObjectMeta{ @@ -88,13 +110,20 @@ var _ = Describe("EthernetSegment Controller", func() { Mode: v1alpha1.SwitchportModeTrunk, }, Aggregation: &v1alpha1.Aggregation{ - MemberInterfaceRefs: []v1alpha1.LocalObjectReference{{Name: "eth1"}}, + MemberInterfaceRefs: []v1alpha1.LocalObjectReference{{Name: name + "-member"}}, ControlProtocol: v1alpha1.ControlProtocol{Mode: v1alpha1.LACPModeActive}, }, }, } Expect(k8sClient.Create(ctx, intf)).To(Succeed()) + By("Waiting for the Interface to be configured") + Eventually(func(g Gomega) { + i := &v1alpha1.Interface{} + g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(intf), i)).To(Succeed()) + g.Expect(conditions.IsConfigured(i)).To(BeTrue()) + }).Should(Succeed()) + By("Creating an EthernetSegment") es := &v1alpha1.EthernetSegment{ ObjectMeta: metav1.ObjectMeta{ @@ -305,6 +334,21 @@ var _ = Describe("EthernetSegment Controller", func() { }) It("Should handle EthernetSegment referencing Interface without switchport", func() { + By("Creating a Physical member Interface") + memberIntf := &v1alpha1.Interface{ + ObjectMeta: metav1.ObjectMeta{ + Name: name + "-member", + Namespace: metav1.NamespaceDefault, + }, + Spec: v1alpha1.InterfaceSpec{ + DeviceRef: v1alpha1.LocalObjectReference{Name: name}, + Name: "eth1", + Type: v1alpha1.InterfaceTypePhysical, + AdminState: v1alpha1.AdminStateUp, + }, + } + Expect(k8sClient.Create(ctx, memberIntf)).To(Succeed()) + By("Creating an Aggregate Interface without switchport config") intf := &v1alpha1.Interface{ ObjectMeta: metav1.ObjectMeta{ @@ -317,13 +361,20 @@ var _ = Describe("EthernetSegment Controller", func() { Type: v1alpha1.InterfaceTypeAggregate, AdminState: v1alpha1.AdminStateUp, Aggregation: &v1alpha1.Aggregation{ - MemberInterfaceRefs: []v1alpha1.LocalObjectReference{{Name: "eth1"}}, + MemberInterfaceRefs: []v1alpha1.LocalObjectReference{{Name: name + "-member"}}, ControlProtocol: v1alpha1.ControlProtocol{Mode: v1alpha1.LACPModeActive}, }, }, } Expect(k8sClient.Create(ctx, intf)).To(Succeed()) + By("Waiting for the Interface to be configured") + Eventually(func(g Gomega) { + i := &v1alpha1.Interface{} + g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(intf), i)).To(Succeed()) + g.Expect(conditions.IsConfigured(i)).To(BeTrue()) + }).Should(Succeed()) + By("Creating an EthernetSegment referencing the Interface") es := &v1alpha1.EthernetSegment{ ObjectMeta: metav1.ObjectMeta{ @@ -356,6 +407,21 @@ var _ = Describe("EthernetSegment Controller", func() { }).Should(Succeed()) }) It("Should auto-derive ESI when ESIType is MAC and ESI is omitted", func() { + By("Creating a Physical member Interface") + memberIntf := &v1alpha1.Interface{ + ObjectMeta: metav1.ObjectMeta{ + Name: name + "-member", + Namespace: metav1.NamespaceDefault, + }, + Spec: v1alpha1.InterfaceSpec{ + DeviceRef: v1alpha1.LocalObjectReference{Name: name}, + Name: "eth1", + Type: v1alpha1.InterfaceTypePhysical, + AdminState: v1alpha1.AdminStateUp, + }, + } + Expect(k8sClient.Create(ctx, memberIntf)).To(Succeed()) + By("Creating an Aggregate Interface with switchport config") intf := &v1alpha1.Interface{ ObjectMeta: metav1.ObjectMeta{ @@ -371,13 +437,20 @@ var _ = Describe("EthernetSegment Controller", func() { Mode: v1alpha1.SwitchportModeTrunk, }, Aggregation: &v1alpha1.Aggregation{ - MemberInterfaceRefs: []v1alpha1.LocalObjectReference{{Name: "eth1"}}, + MemberInterfaceRefs: []v1alpha1.LocalObjectReference{{Name: name + "-member"}}, ControlProtocol: v1alpha1.ControlProtocol{Mode: v1alpha1.LACPModeActive}, }, }, } Expect(k8sClient.Create(ctx, intf)).To(Succeed()) + By("Waiting for the Interface to be configured") + Eventually(func(g Gomega) { + i := &v1alpha1.Interface{} + g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(intf), i)).To(Succeed()) + g.Expect(conditions.IsConfigured(i)).To(BeTrue()) + }).Should(Succeed()) + By("Creating an EthernetSegment with ESIType MAC and no explicit ESI") es := &v1alpha1.EthernetSegment{ ObjectMeta: metav1.ObjectMeta{ From dd77061bd6d792b9d66edb35764027d22fa76e06 Mon Sep 17 00:00:00 2001 From: Adam Trizuljak Date: Fri, 21 Aug 2026 13:12:43 +0200 Subject: [PATCH 12/12] Review, remove unnecessary DeferCleanup of interfaces Signed-off-by: Adam Trizuljak --- internal/controller/core/bgp_peer_controller_test.go | 9 --------- 1 file changed, 9 deletions(-) diff --git a/internal/controller/core/bgp_peer_controller_test.go b/internal/controller/core/bgp_peer_controller_test.go index 9a8aacfb5..059192c5e 100644 --- a/internal/controller/core/bgp_peer_controller_test.go +++ b/internal/controller/core/bgp_peer_controller_test.go @@ -316,14 +316,6 @@ var _ = Describe("BGPPeer Controller", func() { }, } Expect(k8sClient.Create(ctx, differentDevice)).To(Succeed()) - DeferCleanup(func() { - intfList := &v1alpha1.InterfaceList{} - Expect(k8sClient.List(ctx, intfList, client.InNamespace(metav1.NamespaceDefault), client.MatchingLabels{v1alpha1.DeviceLabel: differentDevice.Name})).To(Succeed()) - for i := range intfList.Items { - Expect(client.IgnoreNotFound(k8sClient.Delete(ctx, &intfList.Items[i]))).To(Succeed()) - } - Expect(client.IgnoreNotFound(k8sClient.Delete(ctx, differentDevice))).To(Succeed()) - }) By("Waiting for the different Device to be in Running phase") Eventually(func(g Gomega) { @@ -497,7 +489,6 @@ var _ = Describe("BGPPeer Controller", func() { }) It("Should not reconcile iBGP peer if local-as is set", func() { - // By("Creating a BGP resource for the Device") By("Creating a BGP resource for the Device") bgp := &v1alpha1.BGP{ ObjectMeta: metav1.ObjectMeta{