diff --git a/api/cisco/nx/v1alpha1/bordergateway_types.go b/api/cisco/nx/v1alpha1/bordergateway_types.go index c712be57..7b4fe386 100644 --- a/api/cisco/nx/v1alpha1/bordergateway_types.go +++ b/api/cisco/nx/v1alpha1/bordergateway_types.go @@ -164,6 +164,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="Paused",type=string,JSONPath=`.status.conditions[?(@.type=="Paused")].status`,priority=1 // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" // BorderGateway is the Schema for the bordergateways API diff --git a/api/cisco/nx/v1alpha1/system_types.go b/api/cisco/nx/v1alpha1/system_types.go index 98d86596..f43716ff 100644 --- a/api/cisco/nx/v1alpha1/system_types.go +++ b/api/cisco/nx/v1alpha1/system_types.go @@ -60,6 +60,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="Paused",type=string,JSONPath=`.status.conditions[?(@.type=="Paused")].status`,priority=1 // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" // System is the Schema for the systems API diff --git a/api/cisco/nx/v1alpha1/vpcdomain_types.go b/api/cisco/nx/v1alpha1/vpcdomain_types.go index 47fc96f0..8f2ecc8f 100644 --- a/api/cisco/nx/v1alpha1/vpcdomain_types.go +++ b/api/cisco/nx/v1alpha1/vpcdomain_types.go @@ -257,6 +257,7 @@ const ( // +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="Operational",type=string,JSONPath=`.status.conditions[?(@.type=="Operational")].status`,priority=1 +// +kubebuilder:printcolumn:name="Paused",type=string,JSONPath=`.status.conditions[?(@.type=="Paused")].status`,priority=1 // +kubebuilder:printcolumn:name="Peer Status",type=string,JSONPath=`.status.peerStatus`,priority=1 // +kubebuilder:printcolumn:name="Role",type=string,JSONPath=`.status.role`,priority=1 // +kubebuilder:printcolumn:name="Peer Link Interface",type="string",JSONPath=".status.peerLinkIf",priority=1 diff --git a/api/core/v1alpha1/acl_types.go b/api/core/v1alpha1/acl_types.go index 4a160ead..99c9a575 100644 --- a/api/core/v1alpha1/acl_types.go +++ b/api/core/v1alpha1/acl_types.go @@ -121,6 +121,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="Paused",type=string,JSONPath=`.status.conditions[?(@.type=="Paused")].status`,priority=1 // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" // AccessControlList is the Schema for the accesscontrollists API diff --git a/api/core/v1alpha1/banner_types.go b/api/core/v1alpha1/banner_types.go index 26b40bac..3e591b69 100644 --- a/api/core/v1alpha1/banner_types.go +++ b/api/core/v1alpha1/banner_types.go @@ -66,6 +66,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="Paused",type=string,JSONPath=`.status.conditions[?(@.type=="Paused")].status`,priority=1 // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" // Banner is the Schema for the banners API diff --git a/api/core/v1alpha1/bgp_peer_types.go b/api/core/v1alpha1/bgp_peer_types.go index 28a83780..c51b082a 100644 --- a/api/core/v1alpha1/bgp_peer_types.go +++ b/api/core/v1alpha1/bgp_peer_types.go @@ -223,6 +223,7 @@ const ( // +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="Operational",type=string,JSONPath=`.status.conditions[?(@.type=="Operational")].status`,priority=1 +// +kubebuilder:printcolumn:name="Paused",type=string,JSONPath=`.status.conditions[?(@.type=="Paused")].status`,priority=1 // +kubebuilder:printcolumn:name="Session State",type=string,JSONPath=`.status.sessionState`,priority=1 // +kubebuilder:printcolumn:name="Last Established",type="date",JSONPath=`.status.lastEstablishedTime`,priority=1 // +kubebuilder:printcolumn:name="Advertised Prefixes",type=string,JSONPath=`.status.advertisedPrefixesSummary`,priority=1 diff --git a/api/core/v1alpha1/bgp_types.go b/api/core/v1alpha1/bgp_types.go index 1c52b708..db1f94bb 100644 --- a/api/core/v1alpha1/bgp_types.go +++ b/api/core/v1alpha1/bgp_types.go @@ -154,6 +154,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="Paused",type=string,JSONPath=`.status.conditions[?(@.type=="Paused")].status`,priority=1 // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" // BGP is the Schema for the bgp API diff --git a/api/core/v1alpha1/certificate_types.go b/api/core/v1alpha1/certificate_types.go index c46f0295..758c00e3 100644 --- a/api/core/v1alpha1/certificate_types.go +++ b/api/core/v1alpha1/certificate_types.go @@ -56,6 +56,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="Paused",type=string,JSONPath=`.status.conditions[?(@.type=="Paused")].status`,priority=1 // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" // Certificate is the Schema for the certificates API diff --git a/api/core/v1alpha1/device_types.go b/api/core/v1alpha1/device_types.go index 0b82c96c..3d2907c0 100644 --- a/api/core/v1alpha1/device_types.go +++ b/api/core/v1alpha1/device_types.go @@ -155,9 +155,9 @@ type DeviceStatus struct { // +optional Ports []DevicePort `json:"ports,omitempty"` - // PostSummary shows a summary of the port configured, grouped by type, e.g. "1/4 (10g), 3/64 (100g)". + // PortSummary shows a summary of the port configured, grouped by type, e.g. "1/4 (10g), 3/64 (100g)". // +optional - PostSummary string `json:"portSummary,omitempty"` + PortSummary string `json:"portSummary,omitempty"` // The conditions are a list of status objects that describe the state of the Device. //+listType=map @@ -276,9 +276,9 @@ const ( // +kubebuilder:printcolumn:name="SerialNumber",type=string,JSONPath=".status.serialNumber",priority=1 // +kubebuilder:printcolumn:name="FirmwareVersion",type=string,JSONPath=".status.firmwareVersion",priority=1 // +kubebuilder:printcolumn:name="Ports",type=string,JSONPath=".status.portSummary",priority=1 -// +kubebuilder:printcolumn:name="Paused",type=boolean,JSONPath=`.spec.paused`,priority=1 // +kubebuilder:printcolumn:name="Phase",type="string",JSONPath=".status.phase" // +kubebuilder:printcolumn:name="Ready",type=string,JSONPath=`.status.conditions[?(@.type=="Ready")].status` +// +kubebuilder:printcolumn:name="Paused",type=string,JSONPath=`.status.conditions[?(@.type=="Paused")].status`,priority=1 // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" // Device is the Schema for the devices API. diff --git a/api/core/v1alpha1/dns_types.go b/api/core/v1alpha1/dns_types.go index de54e565..b2d84b7e 100644 --- a/api/core/v1alpha1/dns_types.go +++ b/api/core/v1alpha1/dns_types.go @@ -82,6 +82,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="Paused",type=string,JSONPath=`.status.conditions[?(@.type=="Paused")].status`,priority=1 // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" // DNS is the Schema for the dns API diff --git a/api/core/v1alpha1/evpninstance_types.go b/api/core/v1alpha1/evpninstance_types.go index e5b92f1d..2141b695 100644 --- a/api/core/v1alpha1/evpninstance_types.go +++ b/api/core/v1alpha1/evpninstance_types.go @@ -118,6 +118,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="Paused",type=string,JSONPath=`.status.conditions[?(@.type=="Paused")].status`,priority=1 // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" // EVPNInstance is the Schema for the evpninstances API diff --git a/api/core/v1alpha1/groupversion_info.go b/api/core/v1alpha1/groupversion_info.go index 9a86d27b..f5100391 100644 --- a/api/core/v1alpha1/groupversion_info.go +++ b/api/core/v1alpha1/groupversion_info.go @@ -108,6 +108,11 @@ const ( // be calculated by the controller based on child conditions, if present. ReadyCondition = "Ready" + // PausedCondition indicates whether reconciliation is paused for an object. + // This condition is set to True when the parent Device has spec.paused=true + // or the object has the networking.metal.ironcore.dev/paused annotation. + PausedCondition = "Paused" + // ConfiguredCondition indicates whether the resource has been successfully configured. // This condition indicates whether the desired configuration has been applied to the device // (i.e., all necessary API calls succeeded). @@ -128,6 +133,12 @@ const ( // NotReadyReason indicates that the resource is not ready for use. NotReadyReason = "NotReady" + // PausedReason indicates that reconciliation is paused. + PausedReason = "Paused" + + // NotPausedReason indicates that reconciliation is not paused. + NotPausedReason = "NotPaused" + // UnreachableReason indicates that the controller cannot reach the device. UnreachableReason = "Unreachable" diff --git a/api/core/v1alpha1/interface_types.go b/api/core/v1alpha1/interface_types.go index f5c0727f..1f7c8ba0 100644 --- a/api/core/v1alpha1/interface_types.go +++ b/api/core/v1alpha1/interface_types.go @@ -344,6 +344,7 @@ type InterfaceStatus struct { // +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="Operational",type=string,JSONPath=`.status.conditions[?(@.type=="Operational")].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" // Interface is the Schema for the interfaces API. diff --git a/api/core/v1alpha1/isis_types.go b/api/core/v1alpha1/isis_types.go index bf624cde..41e3e370 100644 --- a/api/core/v1alpha1/isis_types.go +++ b/api/core/v1alpha1/isis_types.go @@ -111,6 +111,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="Paused",type=string,JSONPath=`.status.conditions[?(@.type=="Paused")].status`,priority=1 // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" // ISIS is the Schema for the isis API diff --git a/api/core/v1alpha1/lldp_types.go b/api/core/v1alpha1/lldp_types.go index f7da56b8..865f415d 100644 --- a/api/core/v1alpha1/lldp_types.go +++ b/api/core/v1alpha1/lldp_types.go @@ -72,6 +72,7 @@ type LLDPStatus struct { // +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="Operational",type=string,JSONPath=`.status.conditions[?(@.type=="Operational")].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" // LLDP is the Schema for the lldps API diff --git a/api/core/v1alpha1/managementaccess_types.go b/api/core/v1alpha1/managementaccess_types.go index dad88000..93bfbb46 100644 --- a/api/core/v1alpha1/managementaccess_types.go +++ b/api/core/v1alpha1/managementaccess_types.go @@ -139,6 +139,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="Paused",type=string,JSONPath=`.status.conditions[?(@.type=="Paused")].status`,priority=1 // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" // ManagementAccess is the Schema for the managementaccesses API diff --git a/api/core/v1alpha1/ntp_types.go b/api/core/v1alpha1/ntp_types.go index defdd7be..a72c6cd5 100644 --- a/api/core/v1alpha1/ntp_types.go +++ b/api/core/v1alpha1/ntp_types.go @@ -79,6 +79,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="Paused",type=string,JSONPath=`.status.conditions[?(@.type=="Paused")].status`,priority=1 // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" // NTP is the Schema for the ntp API diff --git a/api/core/v1alpha1/nve_types.go b/api/core/v1alpha1/nve_types.go index ad68f170..7b761a69 100644 --- a/api/core/v1alpha1/nve_types.go +++ b/api/core/v1alpha1/nve_types.go @@ -133,6 +133,7 @@ type NetworkVirtualizationEdgeStatus struct { // +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="Operational",type=string,JSONPath=`.status.conditions[?(@.type=="Operational")].status`,priority=1 +// +kubebuilder:printcolumn:name="Paused",type=string,JSONPath=`.status.conditions[?(@.type=="Paused")].status`,priority=1 // +kubebuilder:printcolumn:name="Source Interface",type=string,JSONPath=`.status.sourceInterfaceName` // +kubebuilder:printcolumn:name="Anycast Interface",type=string,JSONPath=`.status.anycastSourceInterfaceName` // +kubebuilder:printcolumn:name="HostReachability",type=string,JSONPath=`.status.hostReachability` diff --git a/api/core/v1alpha1/ospf_types.go b/api/core/v1alpha1/ospf_types.go index 50f34452..74cb2f3f 100644 --- a/api/core/v1alpha1/ospf_types.go +++ b/api/core/v1alpha1/ospf_types.go @@ -185,6 +185,7 @@ const ( // +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="Operational",type=string,JSONPath=`.status.conditions[?(@.type=="Operational")].status`,priority=1 +// +kubebuilder:printcolumn:name="Paused",type=string,JSONPath=`.status.conditions[?(@.type=="Paused")].status`,priority=1 // +kubebuilder:printcolumn:name="Adjacencies",type=string,JSONPath=`.status.adjacencySummary`,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 d4b89ecb..cbec07bc 100644 --- a/api/core/v1alpha1/pim_types.go +++ b/api/core/v1alpha1/pim_types.go @@ -95,6 +95,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="Paused",type=string,JSONPath=`.status.conditions[?(@.type=="Paused")].status`,priority=1 // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" // PIM is the Schema for the pim API diff --git a/api/core/v1alpha1/prefixset_types.go b/api/core/v1alpha1/prefixset_types.go index 09721ec3..c0cfa18c 100644 --- a/api/core/v1alpha1/prefixset_types.go +++ b/api/core/v1alpha1/prefixset_types.go @@ -95,6 +95,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="Paused",type=string,JSONPath=`.status.conditions[?(@.type=="Paused")].status`,priority=1 // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" // PrefixSet is the Schema for the prefixsets API diff --git a/api/core/v1alpha1/routingpolicy_types.go b/api/core/v1alpha1/routingpolicy_types.go index 4583854e..8fd4c5fa 100644 --- a/api/core/v1alpha1/routingpolicy_types.go +++ b/api/core/v1alpha1/routingpolicy_types.go @@ -151,6 +151,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="Paused",type=string,JSONPath=`.status.conditions[?(@.type=="Paused")].status`,priority=1 // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" // RoutingPolicy is the Schema for the routingpolicies API diff --git a/api/core/v1alpha1/snmp_types.go b/api/core/v1alpha1/snmp_types.go index b8af0ea9..57b00587 100644 --- a/api/core/v1alpha1/snmp_types.go +++ b/api/core/v1alpha1/snmp_types.go @@ -133,6 +133,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="Paused",type=string,JSONPath=`.status.conditions[?(@.type=="Paused")].status`,priority=1 // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" // SNMP is the Schema for the snmp API diff --git a/api/core/v1alpha1/syslog_types.go b/api/core/v1alpha1/syslog_types.go index f489035f..680246c1 100644 --- a/api/core/v1alpha1/syslog_types.go +++ b/api/core/v1alpha1/syslog_types.go @@ -109,6 +109,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="Paused",type=string,JSONPath=`.status.conditions[?(@.type=="Paused")].status`,priority=1 // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" // Syslog is the Schema for the syslogs API diff --git a/api/core/v1alpha1/user_types.go b/api/core/v1alpha1/user_types.go index e5d00424..63a811b8 100644 --- a/api/core/v1alpha1/user_types.go +++ b/api/core/v1alpha1/user_types.go @@ -89,6 +89,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="Paused",type=string,JSONPath=`.status.conditions[?(@.type=="Paused")].status`,priority=1 // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" // User is the Schema for the users API diff --git a/api/core/v1alpha1/vlan_types.go b/api/core/v1alpha1/vlan_types.go index 30f4f66c..cb2fc1d8 100644 --- a/api/core/v1alpha1/vlan_types.go +++ b/api/core/v1alpha1/vlan_types.go @@ -76,6 +76,7 @@ type VLANStatus struct { // +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="Operational",type=string,JSONPath=`.status.conditions[?(@.type=="Operational")].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" // VLAN is the Schema for the vlans API diff --git a/api/core/v1alpha1/vrf_types.go b/api/core/v1alpha1/vrf_types.go index c312df3f..0bd7f437 100644 --- a/api/core/v1alpha1/vrf_types.go +++ b/api/core/v1alpha1/vrf_types.go @@ -119,6 +119,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="Paused",type=string,JSONPath=`.status.conditions[?(@.type=="Paused")].status`,priority=1 // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" // VRF is the Schema for the vrfs API 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 672cac92..4063fb5d 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=="Paused")].status + name: Paused + priority: 1 + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date 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 2b4fb2ae..613d6490 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=="Paused")].status + name: Paused + priority: 1 + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date 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 1d3e7206..bf50a828 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=="Paused")].status + name: Paused + priority: 1 + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date diff --git a/charts/network-operator/templates/crd/bgppeers.networking.metal.ironcore.dev.yaml b/charts/network-operator/templates/crd/bgppeers.networking.metal.ironcore.dev.yaml index f4782199..335f511d 100644 --- a/charts/network-operator/templates/crd/bgppeers.networking.metal.ironcore.dev.yaml +++ b/charts/network-operator/templates/crd/bgppeers.networking.metal.ironcore.dev.yaml @@ -44,6 +44,10 @@ spec: name: Operational priority: 1 type: string + - jsonPath: .status.conditions[?(@.type=="Paused")].status + name: Paused + priority: 1 + type: string - jsonPath: .status.sessionState name: Session State 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 c17d0aef..6b789fcd 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=="Paused")].status + name: Paused + priority: 1 + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date 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 ba6aa4e7..dcbf61f6 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=="Paused")].status + name: Paused + priority: 1 + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date diff --git a/charts/network-operator/templates/crd/devices.networking.metal.ironcore.dev.yaml b/charts/network-operator/templates/crd/devices.networking.metal.ironcore.dev.yaml index af8e1204..e0132342 100644 --- a/charts/network-operator/templates/crd/devices.networking.metal.ironcore.dev.yaml +++ b/charts/network-operator/templates/crd/devices.networking.metal.ironcore.dev.yaml @@ -43,16 +43,16 @@ spec: name: Ports priority: 1 type: string - - jsonPath: .spec.paused - name: Paused - priority: 1 - type: boolean - jsonPath: .status.phase name: Phase type: string - jsonPath: .status.conditions[?(@.type=="Ready")].status name: Ready type: string + - jsonPath: .status.conditions[?(@.type=="Paused")].status + name: Paused + priority: 1 + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date @@ -385,7 +385,7 @@ spec: - Provisioned type: string portSummary: - description: PostSummary shows a summary of the port configured, grouped + description: PortSummary shows a summary of the port configured, grouped by type, e.g. "1/4 (10g), 3/64 (100g)". type: string ports: 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 1c04c44a..8d5bea94 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=="Paused")].status + name: Paused + priority: 1 + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date 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 0fe79be3..e773a1a3 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=="Paused")].status + name: Paused + priority: 1 + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date diff --git a/charts/network-operator/templates/crd/interfaces.networking.metal.ironcore.dev.yaml b/charts/network-operator/templates/crd/interfaces.networking.metal.ironcore.dev.yaml index d329d1c1..dc8a28fd 100644 --- a/charts/network-operator/templates/crd/interfaces.networking.metal.ironcore.dev.yaml +++ b/charts/network-operator/templates/crd/interfaces.networking.metal.ironcore.dev.yaml @@ -51,6 +51,10 @@ spec: name: Operational priority: 1 type: string + - jsonPath: .status.conditions[?(@.type=="Paused")].status + name: Paused + priority: 1 + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date 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 edfe6431..0f796aa6 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=="Paused")].status + name: Paused + priority: 1 + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date diff --git a/charts/network-operator/templates/crd/lldps.networking.metal.ironcore.dev.yaml b/charts/network-operator/templates/crd/lldps.networking.metal.ironcore.dev.yaml index 044b2cd5..f7f97023 100644 --- a/charts/network-operator/templates/crd/lldps.networking.metal.ironcore.dev.yaml +++ b/charts/network-operator/templates/crd/lldps.networking.metal.ironcore.dev.yaml @@ -35,6 +35,10 @@ spec: name: Operational priority: 1 type: string + - jsonPath: .status.conditions[?(@.type=="Paused")].status + name: Paused + priority: 1 + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date 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 2eb61c00..dafbc712 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=="Paused")].status + name: Paused + priority: 1 + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date diff --git a/charts/network-operator/templates/crd/networkvirtualizationedges.networking.metal.ironcore.dev.yaml b/charts/network-operator/templates/crd/networkvirtualizationedges.networking.metal.ironcore.dev.yaml index 24758fc0..786fb8c1 100644 --- a/charts/network-operator/templates/crd/networkvirtualizationedges.networking.metal.ironcore.dev.yaml +++ b/charts/network-operator/templates/crd/networkvirtualizationedges.networking.metal.ironcore.dev.yaml @@ -38,6 +38,10 @@ spec: name: Operational priority: 1 type: string + - jsonPath: .status.conditions[?(@.type=="Paused")].status + name: Paused + priority: 1 + type: string - jsonPath: .status.sourceInterfaceName name: Source Interface type: string 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 1f58092d..ad14d789 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=="Paused")].status + name: Paused + priority: 1 + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date diff --git a/charts/network-operator/templates/crd/ospf.networking.metal.ironcore.dev.yaml b/charts/network-operator/templates/crd/ospf.networking.metal.ironcore.dev.yaml index e7461082..209fa6e7 100644 --- a/charts/network-operator/templates/crd/ospf.networking.metal.ironcore.dev.yaml +++ b/charts/network-operator/templates/crd/ospf.networking.metal.ironcore.dev.yaml @@ -41,6 +41,10 @@ spec: name: Operational priority: 1 type: string + - jsonPath: .status.conditions[?(@.type=="Paused")].status + name: Paused + priority: 1 + type: string - jsonPath: .status.adjacencySummary name: Adjacencies 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 cf79d63c..5e8f944f 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=="Paused")].status + name: Paused + priority: 1 + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date 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 5e8e4419..437571e4 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=="Paused")].status + name: Paused + priority: 1 + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date 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 c072d158..ef7e2609 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=="Paused")].status + name: Paused + priority: 1 + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date 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 879ffd7c..30783899 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=="Paused")].status + name: Paused + priority: 1 + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date 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 8afe1c2b..828b5fce 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=="Paused")].status + name: Paused + priority: 1 + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date 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 24bfefa2..720f807c 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=="Paused")].status + name: Paused + priority: 1 + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date 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 6f9abc7f..37efd089 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=="Paused")].status + name: Paused + priority: 1 + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date diff --git a/charts/network-operator/templates/crd/vlans.networking.metal.ironcore.dev.yaml b/charts/network-operator/templates/crd/vlans.networking.metal.ironcore.dev.yaml index 9bf403a3..b9071700 100644 --- a/charts/network-operator/templates/crd/vlans.networking.metal.ironcore.dev.yaml +++ b/charts/network-operator/templates/crd/vlans.networking.metal.ironcore.dev.yaml @@ -42,6 +42,10 @@ spec: name: Operational priority: 1 type: string + - jsonPath: .status.conditions[?(@.type=="Paused")].status + name: Paused + priority: 1 + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date diff --git a/charts/network-operator/templates/crd/vpcdomains.nx.cisco.networking.metal.ironcore.dev.yaml b/charts/network-operator/templates/crd/vpcdomains.nx.cisco.networking.metal.ironcore.dev.yaml index ea67ac16..c5395b25 100644 --- a/charts/network-operator/templates/crd/vpcdomains.nx.cisco.networking.metal.ironcore.dev.yaml +++ b/charts/network-operator/templates/crd/vpcdomains.nx.cisco.networking.metal.ironcore.dev.yaml @@ -40,6 +40,10 @@ spec: name: Operational priority: 1 type: string + - jsonPath: .status.conditions[?(@.type=="Paused")].status + name: Paused + priority: 1 + type: string - jsonPath: .status.peerStatus name: Peer Status 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 a46114c3..0aa3a1c5 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=="Paused")].status + name: Paused + 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 bc2f2645..5729c4aa 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=="Paused")].status + name: Paused + priority: 1 + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date diff --git a/config/crd/bases/networking.metal.ironcore.dev_banners.yaml b/config/crd/bases/networking.metal.ironcore.dev_banners.yaml index eab0b691..a6bfea19 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=="Paused")].status + name: Paused + priority: 1 + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date diff --git a/config/crd/bases/networking.metal.ironcore.dev_bgp.yaml b/config/crd/bases/networking.metal.ironcore.dev_bgp.yaml index 69870194..95faa75e 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=="Paused")].status + name: Paused + priority: 1 + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date diff --git a/config/crd/bases/networking.metal.ironcore.dev_bgppeers.yaml b/config/crd/bases/networking.metal.ironcore.dev_bgppeers.yaml index 6fc9be75..22e3ffd8 100644 --- a/config/crd/bases/networking.metal.ironcore.dev_bgppeers.yaml +++ b/config/crd/bases/networking.metal.ironcore.dev_bgppeers.yaml @@ -41,6 +41,10 @@ spec: name: Operational priority: 1 type: string + - jsonPath: .status.conditions[?(@.type=="Paused")].status + name: Paused + priority: 1 + type: string - jsonPath: .status.sessionState name: Session State 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 d6614907..8d397448 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=="Paused")].status + name: Paused + priority: 1 + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date diff --git a/config/crd/bases/networking.metal.ironcore.dev_devices.yaml b/config/crd/bases/networking.metal.ironcore.dev_devices.yaml index 62b3b46e..556e1674 100644 --- a/config/crd/bases/networking.metal.ironcore.dev_devices.yaml +++ b/config/crd/bases/networking.metal.ironcore.dev_devices.yaml @@ -40,16 +40,16 @@ spec: name: Ports priority: 1 type: string - - jsonPath: .spec.paused - name: Paused - priority: 1 - type: boolean - jsonPath: .status.phase name: Phase type: string - jsonPath: .status.conditions[?(@.type=="Ready")].status name: Ready type: string + - jsonPath: .status.conditions[?(@.type=="Paused")].status + name: Paused + priority: 1 + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date @@ -382,7 +382,7 @@ spec: - Provisioned type: string portSummary: - description: PostSummary shows a summary of the port configured, grouped + description: PortSummary shows a summary of the port configured, grouped by type, e.g. "1/4 (10g), 3/64 (100g)". type: string ports: diff --git a/config/crd/bases/networking.metal.ironcore.dev_dns.yaml b/config/crd/bases/networking.metal.ironcore.dev_dns.yaml index 086b6d54..f683b39e 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=="Paused")].status + name: Paused + priority: 1 + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date diff --git a/config/crd/bases/networking.metal.ironcore.dev_evpninstances.yaml b/config/crd/bases/networking.metal.ironcore.dev_evpninstances.yaml index 4a89e2f0..8ea5092b 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=="Paused")].status + name: Paused + priority: 1 + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date diff --git a/config/crd/bases/networking.metal.ironcore.dev_interfaces.yaml b/config/crd/bases/networking.metal.ironcore.dev_interfaces.yaml index dc496169..4a95d6c6 100644 --- a/config/crd/bases/networking.metal.ironcore.dev_interfaces.yaml +++ b/config/crd/bases/networking.metal.ironcore.dev_interfaces.yaml @@ -48,6 +48,10 @@ spec: name: Operational priority: 1 type: string + - jsonPath: .status.conditions[?(@.type=="Paused")].status + name: Paused + priority: 1 + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date diff --git a/config/crd/bases/networking.metal.ironcore.dev_isis.yaml b/config/crd/bases/networking.metal.ironcore.dev_isis.yaml index 025d1587..8fb69c40 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=="Paused")].status + name: Paused + priority: 1 + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date diff --git a/config/crd/bases/networking.metal.ironcore.dev_lldps.yaml b/config/crd/bases/networking.metal.ironcore.dev_lldps.yaml index 53368a01..267ebb72 100644 --- a/config/crd/bases/networking.metal.ironcore.dev_lldps.yaml +++ b/config/crd/bases/networking.metal.ironcore.dev_lldps.yaml @@ -32,6 +32,10 @@ spec: name: Operational priority: 1 type: string + - jsonPath: .status.conditions[?(@.type=="Paused")].status + name: Paused + priority: 1 + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date diff --git a/config/crd/bases/networking.metal.ironcore.dev_managementaccesses.yaml b/config/crd/bases/networking.metal.ironcore.dev_managementaccesses.yaml index 8c44f2cb..2e794954 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=="Paused")].status + name: Paused + priority: 1 + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date diff --git a/config/crd/bases/networking.metal.ironcore.dev_networkvirtualizationedges.yaml b/config/crd/bases/networking.metal.ironcore.dev_networkvirtualizationedges.yaml index 3699e34f..ba093f8d 100644 --- a/config/crd/bases/networking.metal.ironcore.dev_networkvirtualizationedges.yaml +++ b/config/crd/bases/networking.metal.ironcore.dev_networkvirtualizationedges.yaml @@ -35,6 +35,10 @@ spec: name: Operational priority: 1 type: string + - jsonPath: .status.conditions[?(@.type=="Paused")].status + name: Paused + priority: 1 + type: string - jsonPath: .status.sourceInterfaceName name: Source Interface type: string diff --git a/config/crd/bases/networking.metal.ironcore.dev_ntp.yaml b/config/crd/bases/networking.metal.ironcore.dev_ntp.yaml index 5c9d21e3..acba2571 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=="Paused")].status + name: Paused + priority: 1 + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date diff --git a/config/crd/bases/networking.metal.ironcore.dev_ospf.yaml b/config/crd/bases/networking.metal.ironcore.dev_ospf.yaml index fdb8cfe8..98ca1fa0 100644 --- a/config/crd/bases/networking.metal.ironcore.dev_ospf.yaml +++ b/config/crd/bases/networking.metal.ironcore.dev_ospf.yaml @@ -38,6 +38,10 @@ spec: name: Operational priority: 1 type: string + - jsonPath: .status.conditions[?(@.type=="Paused")].status + name: Paused + priority: 1 + type: string - jsonPath: .status.adjacencySummary name: Adjacencies 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 2a04e539..f2da9362 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=="Paused")].status + name: Paused + priority: 1 + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date diff --git a/config/crd/bases/networking.metal.ironcore.dev_prefixsets.yaml b/config/crd/bases/networking.metal.ironcore.dev_prefixsets.yaml index d13cd77f..148942c7 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=="Paused")].status + name: Paused + priority: 1 + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date diff --git a/config/crd/bases/networking.metal.ironcore.dev_routingpolicies.yaml b/config/crd/bases/networking.metal.ironcore.dev_routingpolicies.yaml index 608bc64c..288f3df3 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=="Paused")].status + name: Paused + priority: 1 + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date diff --git a/config/crd/bases/networking.metal.ironcore.dev_snmp.yaml b/config/crd/bases/networking.metal.ironcore.dev_snmp.yaml index c0adc2f2..b8c5f402 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=="Paused")].status + name: Paused + priority: 1 + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date diff --git a/config/crd/bases/networking.metal.ironcore.dev_syslogs.yaml b/config/crd/bases/networking.metal.ironcore.dev_syslogs.yaml index 7b00364b..ae12d918 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=="Paused")].status + name: Paused + priority: 1 + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date diff --git a/config/crd/bases/networking.metal.ironcore.dev_users.yaml b/config/crd/bases/networking.metal.ironcore.dev_users.yaml index 837680c1..90f0e4a9 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=="Paused")].status + name: Paused + priority: 1 + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date diff --git a/config/crd/bases/networking.metal.ironcore.dev_vlans.yaml b/config/crd/bases/networking.metal.ironcore.dev_vlans.yaml index acbfd095..eeaebaa2 100644 --- a/config/crd/bases/networking.metal.ironcore.dev_vlans.yaml +++ b/config/crd/bases/networking.metal.ironcore.dev_vlans.yaml @@ -39,6 +39,10 @@ spec: name: Operational priority: 1 type: string + - jsonPath: .status.conditions[?(@.type=="Paused")].status + name: Paused + priority: 1 + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date diff --git a/config/crd/bases/networking.metal.ironcore.dev_vrfs.yaml b/config/crd/bases/networking.metal.ironcore.dev_vrfs.yaml index 21bdcb5f..1f4c62c8 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=="Paused")].status + name: Paused + priority: 1 + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date 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 f03a5f2e..9d33376a 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=="Paused")].status + name: Paused + priority: 1 + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date 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 6afb22e2..b7265af0 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=="Paused")].status + name: Paused + priority: 1 + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date diff --git a/config/crd/bases/nx.cisco.networking.metal.ironcore.dev_vpcdomains.yaml b/config/crd/bases/nx.cisco.networking.metal.ironcore.dev_vpcdomains.yaml index ad3dd0d7..371cee81 100644 --- a/config/crd/bases/nx.cisco.networking.metal.ironcore.dev_vpcdomains.yaml +++ b/config/crd/bases/nx.cisco.networking.metal.ironcore.dev_vpcdomains.yaml @@ -37,6 +37,10 @@ spec: name: Operational priority: 1 type: string + - jsonPath: .status.conditions[?(@.type=="Paused")].status + name: Paused + priority: 1 + type: string - jsonPath: .status.peerStatus name: Peer Status priority: 1 diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 3ba467ec..d4660e6b 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -33,6 +33,7 @@ export default withMermaid({ text: 'Documentation', items: [ { text: 'Overview', link: '/overview' }, + { text: 'Concepts', link: '/concepts/' }, { text: 'Tutorials', link: '/tutorials/' }, { text: 'API References', link: '/api-reference/' }, ], @@ -70,6 +71,13 @@ export default withMermaid({ text: 'Overview', items: [{ text: 'Index', link: '/overview/' }], }, + { + text: 'Concepts', + items: [ + { text: 'Index', link: '/concepts/' }, + { text: 'Pausing Reconciliation', link: '/concepts/pausing' }, + ], + }, { text: 'Tutorials', items: [ diff --git a/docs/api-reference/index.md b/docs/api-reference/index.md index 06e61d0d..5b827127 100644 --- a/docs/api-reference/index.md +++ b/docs/api-reference/index.md @@ -920,7 +920,7 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `paused` _boolean_ | Paused can be used to prevent controllers from processing the Device and its associated objects. | | Optional: \{\}
| +| `paused` _boolean_ | Paused can be used to prevent controllers from processing the Device and its associated objects. | false | Optional: \{\}
| | `endpoint` _[Endpoint](#endpoint)_ | Endpoint contains the connection information for the device. | | Required: \{\}
| | `provisioning` _[Provisioning](#provisioning)_ | Provisioning is an optional configuration for the device provisioning process.
It can be used to provide initial configuration templates or scripts that are applied during the device provisioning. | | Optional: \{\}
| @@ -945,7 +945,7 @@ _Appears in:_ | `firmwareVersion` _string_ | FirmwareVersion is the firmware version running on the Device. | | Optional: \{\}
| | `provisioning` _[ProvisioningInfo](#provisioninginfo) array_ | Provisioning is the list of provisioning attempts for the Device. | | Optional: \{\}
| | `ports` _[DevicePort](#deviceport) array_ | Ports is the list of ports on the Device. | | Optional: \{\}
| -| `portSummary` _string_ | PostSummary shows a summary of the port configured, grouped by type, e.g. "1/4 (10g), 3/64 (100g)". | | Optional: \{\}
| +| `portSummary` _string_ | PortSummary shows a summary of the port configured, grouped by type, e.g. "1/4 (10g), 3/64 (100g)". | | Optional: \{\}
| | `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.35/#condition-v1-meta) array_ | The conditions are a list of status objects that describe the state of the Device. | | Optional: \{\}
| diff --git a/docs/concepts/index.md b/docs/concepts/index.md new file mode 100644 index 00000000..05b3eb2f --- /dev/null +++ b/docs/concepts/index.md @@ -0,0 +1,5 @@ +# Concepts + +This section covers the core concepts behind the Network Operator. + +- [Pausing Reconciliation](./pausing.md) — Temporarily prevent controllers from reconciling resources. diff --git a/docs/concepts/pausing.md b/docs/concepts/pausing.md new file mode 100644 index 00000000..064b6660 --- /dev/null +++ b/docs/concepts/pausing.md @@ -0,0 +1,103 @@ +# Pausing Reconciliation + +Network operators may need to temporarily prevent controllers from reconciling +resources — for example during maintenance windows or manual debugging sessions. + +## Pausing a Device + +Setting `spec.paused: true` on a Device pauses reconciliation of the Device +**and all of its child resources** (Interfaces, VRFs, VLANs, BGP, etc.). + +```yaml +apiVersion: networking.metal.ironcore.dev/v1alpha1 +kind: Device +metadata: + name: leaf-01 +spec: + paused: true + endpoint: + address: 10.0.0.1 +``` + +## Pausing Individual Resources + +The `networking.metal.ironcore.dev/paused` annotation can be applied to any +resource to pause its reconciliation independently of the parent Device. + +```yaml +apiVersion: networking.metal.ironcore.dev/v1alpha1 +kind: VRF +metadata: + name: vrf-prod + annotations: + networking.metal.ironcore.dev/paused: "true" +spec: + deviceRef: + name: leaf-01 + name: prod +``` + +::: tip +You can quickly pause and unpause a resource using `kubectl annotate`: +```bash +# Pause +kubectl annotate vrf vrf-prod networking.metal.ironcore.dev/paused=true + +# Unpause +kubectl annotate vrf vrf-prod networking.metal.ironcore.dev/paused- +``` +::: + +## Paused Condition + +Every resource reflects its pause state in `.status.conditions` with a `Paused` +condition. The `Paused` column is visible with `-o wide`: + +``` +$ kubectl get vrfs -o wide +NAME VRF DEVICE READY PAUSED AGE +vrf-prod prod leaf-01 Unknown True 5m +``` + +The condition message indicates the reason: + +```yaml +conditions: + - type: Paused + status: "True" + reason: Paused + message: "Device spec.paused is set to true" +``` + +## Effect on the Ready Condition + +When a resource is paused, the `Ready` condition is set to `Unknown` with reason `Paused`. + +```yaml +conditions: + - type: Ready + status: "Unknown" + reason: Paused + message: "Reconciliation is paused" + - type: Paused + status: "True" + reason: Paused + message: "Device spec.paused is set to true" +``` + +The `kubectl get` output reflects this: + +``` +$ kubectl get vrfs -o wide +NAME VRF DEVICE READY PAUSED AGE +vrf-prod prod leaf-01 Unknown True 5m +``` + +Once the resource is unpaused, the controller runs a full reconcile and +immediately sets `Ready` back to `True` or `False` based on the observed state. + +::: info Why Unknown and not False? +`False` would imply the resource is broken. `Unknown` is the honest signal: +the operator has stopped actively verifying the resource, so its current +state is simply not known. +::: diff --git a/internal/annotations/annotations.go b/internal/annotations/annotations.go deleted file mode 100644 index 0393d26f..00000000 --- a/internal/annotations/annotations.go +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and IronCore contributors -// SPDX-License-Identifier: Apache-2.0 - -// Package annotations implements annotation helper functions. -package annotations - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/ironcore-dev/network-operator/api/core/v1alpha1" -) - -// IsPaused returns true if the Device is paused or the object has the [v1alpha1.PausedAnnotation]. -func IsPaused(device *v1alpha1.Device, obj metav1.Object) bool { - return device.Spec.Paused || HasPaused(obj) -} - -// HasPaused returns true if the object has the [v1alpha1.PausedAnnotation]. -func HasPaused(obj metav1.Object) bool { - return Has(obj, v1alpha1.PausedAnnotation) -} - -// Has returns true if the object has the specified annotation. -func Has(obj metav1.Object, annotation string) bool { - _, ok := obj.GetAnnotations()[annotation] - return ok -} diff --git a/internal/conditions/conditions.go b/internal/conditions/conditions.go index ab3f56bd..c9bced73 100644 --- a/internal/conditions/conditions.go +++ b/internal/conditions/conditions.go @@ -30,6 +30,17 @@ type Setter interface { SetConditions([]metav1.Condition) } +// Get finds and returns the condition with the given type from the target object. +// Returns nil if the condition is not found. +func Get(target Getter, conditionType string) *metav1.Condition { + return meta.FindStatusCondition(target.GetConditions(), conditionType) +} + +// GetTopLevelCondition finds and returns the top level "Ready" condition. +func GetTopLevelCondition(target Getter) *metav1.Condition { + return Get(target, v1alpha1.ReadyCondition) +} + // Set adds or updates a condition on the target object. // It returns true if the condition was changed, false otherwise. func Set(target Setter, condition metav1.Condition) (changed bool) { @@ -73,7 +84,7 @@ func IsReady(target Getter) bool { // IsConfigured looks at the [v1alpha1.ConfiguredCondition] condition type and returns true // if that condition is set to true and the observed generation matches the object's generation. func IsConfigured(target Getter) bool { - condition := meta.FindStatusCondition(target.GetConditions(), v1alpha1.ConfiguredCondition) + condition := Get(target, v1alpha1.ConfiguredCondition) if condition == nil { return false } @@ -83,11 +94,6 @@ func IsConfigured(target Getter) bool { return condition.Status == metav1.ConditionTrue } -// GetTopLevelCondition finds and returns the top level condition (Ready Condition). -func GetTopLevelCondition(target Getter) *metav1.Condition { - return meta.FindStatusCondition(target.GetConditions(), v1alpha1.ReadyCondition) -} - // InitializeConditions updates all conditions to Unknown if not set. func InitializeConditions(target Setter, types ...string) (changed bool) { conditions := target.GetConditions() @@ -116,7 +122,7 @@ func RecomputeReady(target Setter) (changed bool) { conditions := target.GetConditions() for _, condition := range conditions { - if condition.Type != v1alpha1.ReadyCondition && condition.Status != metav1.ConditionTrue { + if condition.Type != v1alpha1.ReadyCondition && condition.Type != v1alpha1.PausedCondition && condition.Status != metav1.ConditionTrue { status = metav1.ConditionFalse reason = v1alpha1.NotReadyReason message = "One or more conditions are not ready" diff --git a/internal/controller/cisco/nx/bordergateway_controller.go b/internal/controller/cisco/nx/bordergateway_controller.go index 9da39c73..51458a45 100644 --- a/internal/controller/cisco/nx/bordergateway_controller.go +++ b/internal/controller/cisco/nx/bordergateway_controller.go @@ -29,9 +29,9 @@ import ( nxv1alpha1 "github.com/ironcore-dev/network-operator/api/cisco/nx/v1alpha1" "github.com/ironcore-dev/network-operator/api/core/v1alpha1" - "github.com/ironcore-dev/network-operator/internal/annotations" "github.com/ironcore-dev/network-operator/internal/conditions" "github.com/ironcore-dev/network-operator/internal/deviceutil" + "github.com/ironcore-dev/network-operator/internal/paused" "github.com/ironcore-dev/network-operator/internal/provider" "github.com/ironcore-dev/network-operator/internal/provider/cisco/nxos" "github.com/ironcore-dev/network-operator/internal/resourcelock" @@ -104,9 +104,8 @@ func (r *BorderGatewayReconciler) Reconcile(ctx context.Context, req ctrl.Reques return ctrl.Result{}, err } - if annotations.IsPaused(device, obj) { - log.Info("Reconciliation is paused for this object") - return ctrl.Result{}, nil + if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { + return ctrl.Result{Requeue: requeue}, err } if err := r.Locker.AcquireLock(ctx, device.Name, "cisco-nx-border-gateway-controller"); err != nil { diff --git a/internal/controller/cisco/nx/bordergateway_controller_test.go b/internal/controller/cisco/nx/bordergateway_controller_test.go index 106b2b7b..bc6e27b9 100644 --- a/internal/controller/cisco/nx/bordergateway_controller_test.go +++ b/internal/controller/cisco/nx/bordergateway_controller_test.go @@ -124,9 +124,11 @@ 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(1)) + g.Expect(resource.Status.Conditions).To(HaveLen(2)) 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)) }).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 f82dbf14..27129133 100644 --- a/internal/controller/cisco/nx/system_controller.go +++ b/internal/controller/cisco/nx/system_controller.go @@ -27,9 +27,9 @@ import ( nxv1alpha1 "github.com/ironcore-dev/network-operator/api/cisco/nx/v1alpha1" "github.com/ironcore-dev/network-operator/api/core/v1alpha1" - "github.com/ironcore-dev/network-operator/internal/annotations" "github.com/ironcore-dev/network-operator/internal/conditions" "github.com/ironcore-dev/network-operator/internal/deviceutil" + "github.com/ironcore-dev/network-operator/internal/paused" "github.com/ironcore-dev/network-operator/internal/provider" "github.com/ironcore-dev/network-operator/internal/resourcelock" ) @@ -101,9 +101,8 @@ func (r *SystemReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ c return ctrl.Result{}, err } - if annotations.IsPaused(device, obj) { - log.Info("Reconciliation is paused for this object") - return ctrl.Result{}, nil + if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { + return ctrl.Result{Requeue: requeue}, err } if err := r.Locker.AcquireLock(ctx, device.Name, "cisco-nx-system-controller"); err != nil { diff --git a/internal/controller/cisco/nx/system_controller_test.go b/internal/controller/cisco/nx/system_controller_test.go index ede427ca..1553a5c6 100644 --- a/internal/controller/cisco/nx/system_controller_test.go +++ b/internal/controller/cisco/nx/system_controller_test.go @@ -103,9 +103,11 @@ 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(1)) + g.Expect(resource.Status.Conditions).To(HaveLen(2)) 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)) }).Should(Succeed()) By("Ensuring the resource is created in the provider") diff --git a/internal/controller/cisco/nx/vpcdomain_controller.go b/internal/controller/cisco/nx/vpcdomain_controller.go index 5252a732..115d282e 100644 --- a/internal/controller/cisco/nx/vpcdomain_controller.go +++ b/internal/controller/cisco/nx/vpcdomain_controller.go @@ -28,8 +28,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/reconcile" - "github.com/ironcore-dev/network-operator/internal/annotations" "github.com/ironcore-dev/network-operator/internal/conditions" + "github.com/ironcore-dev/network-operator/internal/paused" "github.com/ironcore-dev/network-operator/internal/provider" "github.com/ironcore-dev/network-operator/internal/resourcelock" @@ -114,9 +114,8 @@ func (r *VPCDomainReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( return ctrl.Result{}, err } - if annotations.IsPaused(device, obj) { - log.Info("Reconciliation is paused for this object") - return ctrl.Result{}, nil + if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { + return ctrl.Result{Requeue: requeue}, err } if err := r.Locker.AcquireLock(ctx, device.Name, "cisco-nx-vpcdomain-controller"); err != nil { diff --git a/internal/controller/cisco/nx/vpcdomain_controller_test.go b/internal/controller/cisco/nx/vpcdomain_controller_test.go index 3a13a1e2..7fd2243c 100644 --- a/internal/controller/cisco/nx/vpcdomain_controller_test.go +++ b/internal/controller/cisco/nx/vpcdomain_controller_test.go @@ -170,13 +170,15 @@ var _ = Describe("VPCDomain Controller", func() { Eventually(func(g Gomega) { resource := &nxv1.VPCDomain{} g.Expect(k8sClient.Get(ctx, vpcdomainKey, resource)).To(Succeed()) - g.Expect(resource.Status.Conditions).To(HaveLen(3)) + g.Expect(resource.Status.Conditions).To(HaveLen(4)) g.Expect(resource.Status.Conditions[0].Type).To(Equal(corev1.ReadyCondition)) g.Expect(resource.Status.Conditions[0].Status).To(Equal(metav1.ConditionTrue)) g.Expect(resource.Status.Conditions[1].Type).To(Equal(corev1.ConfiguredCondition)) g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionTrue)) g.Expect(resource.Status.Conditions[2].Type).To(Equal(corev1.OperationalCondition)) g.Expect(resource.Status.Conditions[2].Status).To(Equal(metav1.ConditionTrue)) + g.Expect(resource.Status.Conditions[3].Type).To(Equal(corev1.PausedCondition)) + g.Expect(resource.Status.Conditions[3].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) By("Ensuring the resource is created in the provider") @@ -339,12 +341,14 @@ var _ = Describe("VPCDomain Controller", func() { Eventually(func(g Gomega) { resource := &nxv1.VPCDomain{} g.Expect(k8sClient.Get(ctx, vpcdomainKey, 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(corev1.ReadyCondition)) g.Expect(resource.Status.Conditions[0].Status).To(Equal(metav1.ConditionFalse)) g.Expect(resource.Status.Conditions[1].Type).To(Equal(corev1.ConfiguredCondition)) g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionFalse)) g.Expect(resource.Status.Conditions[1].Reason).To(Equal(corev1.WaitingForDependenciesReason)) + g.Expect(resource.Status.Conditions[2].Type).To(Equal(corev1.PausedCondition)) + g.Expect(resource.Status.Conditions[2].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) }) @@ -379,12 +383,14 @@ var _ = Describe("VPCDomain Controller", func() { Eventually(func(g Gomega) { resource := &nxv1.VPCDomain{} g.Expect(k8sClient.Get(ctx, vpcdomainKey, 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(corev1.ReadyCondition)) g.Expect(resource.Status.Conditions[0].Status).To(Equal(metav1.ConditionFalse)) g.Expect(resource.Status.Conditions[1].Type).To(Equal(corev1.ConfiguredCondition)) g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionFalse)) g.Expect(resource.Status.Conditions[1].Reason).To(Equal(corev1.InvalidInterfaceTypeReason)) + g.Expect(resource.Status.Conditions[2].Type).To(Equal(corev1.PausedCondition)) + g.Expect(resource.Status.Conditions[2].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) }) @@ -419,12 +425,14 @@ var _ = Describe("VPCDomain Controller", func() { Eventually(func(g Gomega) { resource := &nxv1.VPCDomain{} g.Expect(k8sClient.Get(ctx, vpcdomainKey, 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(corev1.ReadyCondition)) g.Expect(resource.Status.Conditions[0].Status).To(Equal(metav1.ConditionFalse)) g.Expect(resource.Status.Conditions[1].Type).To(Equal(corev1.ConfiguredCondition)) g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionFalse)) g.Expect(resource.Status.Conditions[1].Reason).To(Equal(corev1.CrossDeviceReferenceReason)) + g.Expect(resource.Status.Conditions[2].Type).To(Equal(corev1.PausedCondition)) + g.Expect(resource.Status.Conditions[2].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) }) @@ -459,12 +467,14 @@ var _ = Describe("VPCDomain Controller", func() { Eventually(func(g Gomega) { resource := &nxv1.VPCDomain{} g.Expect(k8sClient.Get(ctx, vpcdomainKey, 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(corev1.ReadyCondition)) g.Expect(resource.Status.Conditions[0].Status).To(Equal(metav1.ConditionFalse)) g.Expect(resource.Status.Conditions[1].Type).To(Equal(corev1.ConfiguredCondition)) g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionFalse)) g.Expect(resource.Status.Conditions[1].Reason).To(Equal(corev1.WaitingForDependenciesReason)) + g.Expect(resource.Status.Conditions[2].Type).To(Equal(corev1.PausedCondition)) + g.Expect(resource.Status.Conditions[2].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) }) @@ -499,12 +509,14 @@ var _ = Describe("VPCDomain Controller", func() { Eventually(func(g Gomega) { resource := &nxv1.VPCDomain{} g.Expect(k8sClient.Get(ctx, vpcdomainKey, 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(corev1.ReadyCondition)) g.Expect(resource.Status.Conditions[0].Status).To(Equal(metav1.ConditionFalse)) g.Expect(resource.Status.Conditions[1].Type).To(Equal(corev1.ConfiguredCondition)) g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionFalse)) g.Expect(resource.Status.Conditions[1].Reason).To(Equal(corev1.CrossDeviceReferenceReason)) + g.Expect(resource.Status.Conditions[2].Type).To(Equal(corev1.PausedCondition)) + g.Expect(resource.Status.Conditions[2].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) }) }) diff --git a/internal/controller/core/acl_controller.go b/internal/controller/core/acl_controller.go index 397842f3..47d1b31d 100644 --- a/internal/controller/core/acl_controller.go +++ b/internal/controller/core/acl_controller.go @@ -29,9 +29,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/ironcore-dev/network-operator/api/core/v1alpha1" - "github.com/ironcore-dev/network-operator/internal/annotations" "github.com/ironcore-dev/network-operator/internal/conditions" "github.com/ironcore-dev/network-operator/internal/deviceutil" + "github.com/ironcore-dev/network-operator/internal/paused" "github.com/ironcore-dev/network-operator/internal/provider" "github.com/ironcore-dev/network-operator/internal/resourcelock" ) @@ -103,9 +103,8 @@ func (r *AccessControlListReconciler) Reconcile(ctx context.Context, req ctrl.Re return ctrl.Result{}, err } - if annotations.IsPaused(device, obj) { - log.Info("Reconciliation is paused for this object") - return ctrl.Result{}, nil + if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { + return ctrl.Result{Requeue: requeue}, err } if err := r.Locker.AcquireLock(ctx, device.Name, "acl-controller"); err != nil { diff --git a/internal/controller/core/acl_controller_test.go b/internal/controller/core/acl_controller_test.go index 77b47d46..dda2bd84 100644 --- a/internal/controller/core/acl_controller_test.go +++ b/internal/controller/core/acl_controller_test.go @@ -120,9 +120,11 @@ 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(1)) + g.Expect(resource.Status.Conditions).To(HaveLen(2)) 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)) }).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 1a3cc77b..1ccd246a 100644 --- a/internal/controller/core/banner_controller.go +++ b/internal/controller/core/banner_controller.go @@ -30,10 +30,10 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/ironcore-dev/network-operator/api/core/v1alpha1" - "github.com/ironcore-dev/network-operator/internal/annotations" "github.com/ironcore-dev/network-operator/internal/clientutil" "github.com/ironcore-dev/network-operator/internal/conditions" "github.com/ironcore-dev/network-operator/internal/deviceutil" + "github.com/ironcore-dev/network-operator/internal/paused" "github.com/ironcore-dev/network-operator/internal/provider" "github.com/ironcore-dev/network-operator/internal/resourcelock" ) @@ -107,9 +107,8 @@ func (r *BannerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ c return ctrl.Result{}, err } - if annotations.IsPaused(device, obj) { - log.Info("Reconciliation is paused for this object") - return ctrl.Result{}, nil + if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { + return ctrl.Result{Requeue: requeue}, err } if err := r.Locker.AcquireLock(ctx, device.Name, "banner-controller"); err != nil { diff --git a/internal/controller/core/banner_controller_test.go b/internal/controller/core/banner_controller_test.go index 93b4cf30..0a944ce9 100644 --- a/internal/controller/core/banner_controller_test.go +++ b/internal/controller/core/banner_controller_test.go @@ -104,9 +104,11 @@ 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(1)) + g.Expect(resource.Status.Conditions).To(HaveLen(2)) 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)) }).Should(Succeed()) By("Ensuring the resource is created in the provider") @@ -163,9 +165,11 @@ 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(1)) + g.Expect(resource.Status.Conditions).To(HaveLen(2)) 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)) }).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 70d137d2..24fe0f1b 100644 --- a/internal/controller/core/bgp_controller.go +++ b/internal/controller/core/bgp_controller.go @@ -29,9 +29,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/ironcore-dev/network-operator/api/core/v1alpha1" - "github.com/ironcore-dev/network-operator/internal/annotations" "github.com/ironcore-dev/network-operator/internal/conditions" "github.com/ironcore-dev/network-operator/internal/deviceutil" + "github.com/ironcore-dev/network-operator/internal/paused" "github.com/ironcore-dev/network-operator/internal/provider" "github.com/ironcore-dev/network-operator/internal/resourcelock" ) @@ -107,9 +107,8 @@ func (r *BGPReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl return ctrl.Result{}, err } - if annotations.IsPaused(device, obj) { - log.Info("Reconciliation is paused for this object") - return ctrl.Result{}, nil + if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { + return ctrl.Result{Requeue: requeue}, err } if err := r.Locker.AcquireLock(ctx, device.Name, "bgp-controller"); err != nil { diff --git a/internal/controller/core/bgp_controller_test.go b/internal/controller/core/bgp_controller_test.go index 82fa5cdd..2cf5534d 100644 --- a/internal/controller/core/bgp_controller_test.go +++ b/internal/controller/core/bgp_controller_test.go @@ -102,9 +102,11 @@ var _ = Describe("BGP Controller", func() { Eventually(func(g Gomega) { resource := &v1alpha1.BGP{} g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) - g.Expect(resource.Status.Conditions).To(HaveLen(1)) + g.Expect(resource.Status.Conditions).To(HaveLen(2)) 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)) }).Should(Succeed()) By("Ensuring the resource is created in the provider") diff --git a/internal/controller/core/bgp_peer_controller.go b/internal/controller/core/bgp_peer_controller.go index b23ce666..65714394 100644 --- a/internal/controller/core/bgp_peer_controller.go +++ b/internal/controller/core/bgp_peer_controller.go @@ -32,9 +32,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/ironcore-dev/network-operator/api/core/v1alpha1" - "github.com/ironcore-dev/network-operator/internal/annotations" "github.com/ironcore-dev/network-operator/internal/conditions" "github.com/ironcore-dev/network-operator/internal/deviceutil" + "github.com/ironcore-dev/network-operator/internal/paused" "github.com/ironcore-dev/network-operator/internal/provider" "github.com/ironcore-dev/network-operator/internal/resourcelock" ) @@ -110,9 +110,8 @@ func (r *BGPPeerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ return ctrl.Result{}, err } - if annotations.IsPaused(device, obj) { - log.Info("Reconciliation is paused for this object") - return ctrl.Result{}, nil + if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { + return ctrl.Result{Requeue: requeue}, err } if err := r.Locker.AcquireLock(ctx, device.Name, "bgppeer-controller"); err != nil { diff --git a/internal/controller/core/bgp_peer_controller_test.go b/internal/controller/core/bgp_peer_controller_test.go index ac01e3c4..36cae963 100644 --- a/internal/controller/core/bgp_peer_controller_test.go +++ b/internal/controller/core/bgp_peer_controller_test.go @@ -102,13 +102,15 @@ var _ = Describe("BGPPeer Controller", func() { Eventually(func(g Gomega) { resource := &v1alpha1.BGPPeer{} g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) - g.Expect(resource.Status.Conditions).To(HaveLen(3)) + g.Expect(resource.Status.Conditions).To(HaveLen(4)) 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.ConfiguredCondition)) g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionTrue)) g.Expect(resource.Status.Conditions[2].Type).To(Equal(v1alpha1.OperationalCondition)) g.Expect(resource.Status.Conditions[2].Status).To(Equal(metav1.ConditionTrue)) + g.Expect(resource.Status.Conditions[3].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[3].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) By("Verifying the BGP peer is configured in the provider") @@ -154,13 +156,15 @@ var _ = Describe("BGPPeer Controller", func() { Eventually(func(g Gomega) { resource := &v1alpha1.BGPPeer{} g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) - g.Expect(resource.Status.Conditions).To(HaveLen(3)) + g.Expect(resource.Status.Conditions).To(HaveLen(4)) 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.ConfiguredCondition)) g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionTrue)) g.Expect(resource.Status.Conditions[2].Type).To(Equal(v1alpha1.OperationalCondition)) g.Expect(resource.Status.Conditions[2].Status).To(Equal(metav1.ConditionTrue)) + g.Expect(resource.Status.Conditions[3].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[3].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) By("Verifying the BGP peer is configured in the provider") @@ -191,7 +195,7 @@ var _ = Describe("BGPPeer Controller", func() { Eventually(func(g Gomega) { resource := &v1alpha1.BGPPeer{} g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) - g.Expect(resource.Status.Conditions).To(HaveLen(3)) + g.Expect(resource.Status.Conditions).To(HaveLen(4)) 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[1].Type).To(Equal(v1alpha1.ConfiguredCondition)) @@ -199,6 +203,8 @@ var _ = Describe("BGPPeer Controller", func() { g.Expect(resource.Status.Conditions[1].Reason).To(Equal(v1alpha1.InterfaceNotFoundReason)) g.Expect(resource.Status.Conditions[2].Type).To(Equal(v1alpha1.OperationalCondition)) g.Expect(resource.Status.Conditions[2].Status).To(Equal(metav1.ConditionUnknown)) + g.Expect(resource.Status.Conditions[3].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[3].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) }) @@ -239,7 +245,7 @@ var _ = Describe("BGPPeer Controller", func() { Eventually(func(g Gomega) { resource := &v1alpha1.BGPPeer{} g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) - g.Expect(resource.Status.Conditions).To(HaveLen(3)) + g.Expect(resource.Status.Conditions).To(HaveLen(4)) 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[1].Type).To(Equal(v1alpha1.ConfiguredCondition)) @@ -247,6 +253,8 @@ var _ = Describe("BGPPeer Controller", func() { g.Expect(resource.Status.Conditions[1].Reason).To(Equal(v1alpha1.CrossDeviceReferenceReason)) g.Expect(resource.Status.Conditions[2].Type).To(Equal(v1alpha1.OperationalCondition)) g.Expect(resource.Status.Conditions[2].Status).To(Equal(metav1.ConditionUnknown)) + g.Expect(resource.Status.Conditions[3].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[3].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) }) }) diff --git a/internal/controller/core/certificate_controller.go b/internal/controller/core/certificate_controller.go index c5ae21cf..12c2e657 100644 --- a/internal/controller/core/certificate_controller.go +++ b/internal/controller/core/certificate_controller.go @@ -30,10 +30,10 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/ironcore-dev/network-operator/api/core/v1alpha1" - "github.com/ironcore-dev/network-operator/internal/annotations" "github.com/ironcore-dev/network-operator/internal/clientutil" "github.com/ironcore-dev/network-operator/internal/conditions" "github.com/ironcore-dev/network-operator/internal/deviceutil" + "github.com/ironcore-dev/network-operator/internal/paused" "github.com/ironcore-dev/network-operator/internal/provider" "github.com/ironcore-dev/network-operator/internal/resourcelock" ) @@ -106,9 +106,8 @@ func (r *CertificateReconciler) Reconcile(ctx context.Context, req ctrl.Request) return ctrl.Result{}, err } - if annotations.IsPaused(device, obj) { - log.Info("Reconciliation is paused for this object") - return ctrl.Result{}, nil + if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { + return ctrl.Result{Requeue: requeue}, err } if err := r.Locker.AcquireLock(ctx, device.Name, "certificate-controller"); err != nil { diff --git a/internal/controller/core/certificate_controller_test.go b/internal/controller/core/certificate_controller_test.go index abd8f648..8d345b18 100644 --- a/internal/controller/core/certificate_controller_test.go +++ b/internal/controller/core/certificate_controller_test.go @@ -129,9 +129,11 @@ 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(1)) + g.Expect(resource.Status.Conditions).To(HaveLen(2)) 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)) }).Should(Succeed()) By("Ensuring the resource is created in the provider") diff --git a/internal/controller/core/device_controller.go b/internal/controller/core/device_controller.go index 853717f5..1f3c0b7b 100644 --- a/internal/controller/core/device_controller.go +++ b/internal/controller/core/device_controller.go @@ -29,9 +29,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/ironcore-dev/network-operator/api/core/v1alpha1" - "github.com/ironcore-dev/network-operator/internal/annotations" "github.com/ironcore-dev/network-operator/internal/conditions" "github.com/ironcore-dev/network-operator/internal/deviceutil" + "github.com/ironcore-dev/network-operator/internal/paused" "github.com/ironcore-dev/network-operator/internal/provider" ) @@ -94,9 +94,8 @@ func (r *DeviceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ c return ctrl.Result{}, err } - if annotations.IsPaused(obj, obj) { - log.Info("Reconciliation is paused for this object") - return ctrl.Result{}, nil + if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, obj, obj); isPaused || requeue || err != nil { + return ctrl.Result{Requeue: requeue}, err } conn, err := deviceutil.GetDeviceConnection(ctx, r, obj) @@ -300,7 +299,7 @@ func (r *DeviceReconciler) reconcile(ctx context.Context, device *v1alpha1.Devic slices.Sort(device.Status.Ports[i].SupportedSpeedsGbps) } - device.Status.PostSummary = PortSummary(device.Status.Ports) + device.Status.PortSummary = PortSummary(device.Status.Ports) info, err := prov.GetDeviceInfo(ctx) if err != nil { @@ -323,9 +322,6 @@ func (r *DeviceReconciler) reconcile(ctx context.Context, device *v1alpha1.Devic } func (r *DeviceReconciler) reconcileMaintenance(ctx context.Context, obj *v1alpha1.Device, prov provider.DeviceProvider, conn *deviceutil.Connection) error { - if obj.Annotations == nil { - return nil - } action, ok := obj.Annotations[v1alpha1.DeviceMaintenanceAnnotation] if !ok { return nil diff --git a/internal/controller/core/device_controller_test.go b/internal/controller/core/device_controller_test.go index b12071e3..46a47658 100644 --- a/internal/controller/core/device_controller_test.go +++ b/internal/controller/core/device_controller_test.go @@ -80,10 +80,12 @@ var _ = Describe("Device Controller", func() { resource := &v1alpha1.Device{} g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) g.Expect(resource.Status.Phase).To(Equal(v1alpha1.DevicePhaseRunning)) - g.Expect(resource.Status.Conditions).To(HaveLen(1)) + g.Expect(resource.Status.Conditions).To(HaveLen(2)) 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[0].Reason).To(Equal(v1alpha1.ReadyReason)) + g.Expect(resource.Status.Conditions[1].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) By("Creating the custom resource for the Kind Interface") @@ -109,9 +111,11 @@ var _ = Describe("Device Controller", func() { g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) g.Expect(resource.Status.Phase).To(Equal(v1alpha1.DevicePhaseRunning)) - g.Expect(resource.Status.Conditions).To(HaveLen(1)) + g.Expect(resource.Status.Conditions).To(HaveLen(2)) 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.Manufacturer).To(Equal("Manufacturer")) g.Expect(resource.Status.Model).To(Equal("Model")) @@ -125,7 +129,7 @@ var _ = Describe("Device Controller", func() { g.Expect(resource.Status.Ports[0].Transceiver).To(Equal("QSFP-DD")) g.Expect(resource.Status.Ports[0].InterfaceRef).ToNot(BeNil()) g.Expect(resource.Status.Ports[0].InterfaceRef.Name).To(Equal(name)) - g.Expect(resource.Status.PostSummary).To(Equal("1/8 (10g)")) + g.Expect(resource.Status.PortSummary).To(Equal("1/8 (10g)")) }).Should(Succeed()) By("Cleanup the specific resource instance Interface") @@ -168,10 +172,12 @@ var _ = Describe("Device Controller", func() { resource := &v1alpha1.Device{} g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) g.Expect(resource.Status.Phase).To(Equal(v1alpha1.DevicePhaseProvisioning)) - g.Expect(resource.Status.Conditions).To(HaveLen(1)) + g.Expect(resource.Status.Conditions).To(HaveLen(2)) 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.ProvisioningReason)) + g.Expect(resource.Status.Conditions[1].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) }) @@ -219,10 +225,12 @@ var _ = Describe("Device Controller", func() { resource := &v1alpha1.Device{} g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) g.Expect(resource.Status.Phase).To(Equal(v1alpha1.DevicePhaseRunning)) - g.Expect(resource.Status.Conditions).To(HaveLen(1)) + g.Expect(resource.Status.Conditions).To(HaveLen(2)) 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[0].Reason).To(Equal(v1alpha1.ReadyReason)) + g.Expect(resource.Status.Conditions[1].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) }) @@ -259,8 +267,10 @@ var _ = Describe("Device Controller", func() { resource := &v1alpha1.Device{} g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) g.Expect(resource.Status.Phase).To(Equal(v1alpha1.DevicePhaseProvisioning)) - g.Expect(resource.Status.Conditions).To(HaveLen(1)) + g.Expect(resource.Status.Conditions).To(HaveLen(2)) g.Expect(resource.Status.Conditions[0].Type).To(Equal(v1alpha1.ReadyCondition)) + g.Expect(resource.Status.Conditions[1].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) By("Setting the device to Running phase") @@ -273,8 +283,10 @@ var _ = Describe("Device Controller", func() { resource := &v1alpha1.Device{} g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) g.Expect(resource.Status.Phase).To(Equal(v1alpha1.DevicePhaseRunning)) - g.Expect(resource.Status.Conditions).To(HaveLen(1)) + g.Expect(resource.Status.Conditions).To(HaveLen(2)) g.Expect(resource.Status.Conditions[0].Type).To(Equal(v1alpha1.ReadyCondition)) + g.Expect(resource.Status.Conditions[1].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) By("Adding the reset-phase annotation to the device") diff --git a/internal/controller/core/dns_controller.go b/internal/controller/core/dns_controller.go index 6367e70d..c84ffaf3 100644 --- a/internal/controller/core/dns_controller.go +++ b/internal/controller/core/dns_controller.go @@ -29,9 +29,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/ironcore-dev/network-operator/api/core/v1alpha1" - "github.com/ironcore-dev/network-operator/internal/annotations" "github.com/ironcore-dev/network-operator/internal/conditions" "github.com/ironcore-dev/network-operator/internal/deviceutil" + "github.com/ironcore-dev/network-operator/internal/paused" "github.com/ironcore-dev/network-operator/internal/provider" "github.com/ironcore-dev/network-operator/internal/resourcelock" ) @@ -103,9 +103,8 @@ func (r *DNSReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl return ctrl.Result{}, err } - if annotations.IsPaused(device, obj) { - log.Info("Reconciliation is paused for this object") - return ctrl.Result{}, nil + if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { + return ctrl.Result{Requeue: requeue}, err } if err := r.Locker.AcquireLock(ctx, device.Name, "dns-controller"); err != nil { diff --git a/internal/controller/core/dns_controller_test.go b/internal/controller/core/dns_controller_test.go index e3e93e16..13fa2450 100644 --- a/internal/controller/core/dns_controller_test.go +++ b/internal/controller/core/dns_controller_test.go @@ -106,9 +106,11 @@ 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(1)) + g.Expect(resource.Status.Conditions).To(HaveLen(2)) 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)) }).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 83a5b0ee..d2191a76 100644 --- a/internal/controller/core/evpninstance_controller.go +++ b/internal/controller/core/evpninstance_controller.go @@ -29,9 +29,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/ironcore-dev/network-operator/api/core/v1alpha1" - "github.com/ironcore-dev/network-operator/internal/annotations" "github.com/ironcore-dev/network-operator/internal/conditions" "github.com/ironcore-dev/network-operator/internal/deviceutil" + "github.com/ironcore-dev/network-operator/internal/paused" "github.com/ironcore-dev/network-operator/internal/provider" "github.com/ironcore-dev/network-operator/internal/resourcelock" ) @@ -105,9 +105,8 @@ func (r *EVPNInstanceReconciler) Reconcile(ctx context.Context, req ctrl.Request return ctrl.Result{}, err } - if annotations.IsPaused(device, obj) { - log.Info("Reconciliation is paused for this object") - return ctrl.Result{}, nil + if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { + return ctrl.Result{Requeue: requeue}, err } if err := r.Locker.AcquireLock(ctx, device.Name, "evpn-instance-controller"); err != nil { diff --git a/internal/controller/core/evpninstance_controller_test.go b/internal/controller/core/evpninstance_controller_test.go index 5adb7515..0b3ca6eb 100644 --- a/internal/controller/core/evpninstance_controller_test.go +++ b/internal/controller/core/evpninstance_controller_test.go @@ -128,9 +128,11 @@ 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(1)) + g.Expect(resource.Status.Conditions).To(HaveLen(2)) 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)) }).Should(Succeed()) By("Verifying the VLAN is labeled with L2VNI label") @@ -182,10 +184,12 @@ 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(1)) + g.Expect(resource.Status.Conditions).To(HaveLen(2)) 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].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) }) @@ -232,10 +236,12 @@ 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(1)) + g.Expect(resource.Status.Conditions).To(HaveLen(2)) 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].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) }) }) diff --git a/internal/controller/core/interface_controller.go b/internal/controller/core/interface_controller.go index 22ebedd9..d47932f3 100644 --- a/internal/controller/core/interface_controller.go +++ b/internal/controller/core/interface_controller.go @@ -31,9 +31,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/ironcore-dev/network-operator/api/core/v1alpha1" - "github.com/ironcore-dev/network-operator/internal/annotations" "github.com/ironcore-dev/network-operator/internal/conditions" "github.com/ironcore-dev/network-operator/internal/deviceutil" + "github.com/ironcore-dev/network-operator/internal/paused" "github.com/ironcore-dev/network-operator/internal/provider" "github.com/ironcore-dev/network-operator/internal/resourcelock" ) @@ -112,9 +112,8 @@ func (r *InterfaceReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( return ctrl.Result{}, err } - if annotations.IsPaused(device, obj) { - log.Info("Reconciliation is paused for this object") - return ctrl.Result{}, nil + if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { + return ctrl.Result{Requeue: requeue}, err } if err := r.Locker.AcquireLock(ctx, device.Name, "interface-controller"); err != nil { diff --git a/internal/controller/core/interface_controller_test.go b/internal/controller/core/interface_controller_test.go index b09383fd..d1cfac08 100644 --- a/internal/controller/core/interface_controller_test.go +++ b/internal/controller/core/interface_controller_test.go @@ -124,13 +124,15 @@ var _ = Describe("Interface Controller", func() { Eventually(func(g Gomega) { resource := &v1alpha1.Interface{} g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) - g.Expect(resource.Status.Conditions).To(HaveLen(3)) + g.Expect(resource.Status.Conditions).To(HaveLen(4)) 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.ConfiguredCondition)) g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionTrue)) g.Expect(resource.Status.Conditions[2].Type).To(Equal(v1alpha1.OperationalCondition)) g.Expect(resource.Status.Conditions[2].Status).To(Equal(metav1.ConditionTrue)) + g.Expect(resource.Status.Conditions[3].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[3].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) By("Verifying the Interface is configured in the provider") @@ -182,13 +184,15 @@ var _ = Describe("Interface Controller", func() { Eventually(func(g Gomega) { resource := &v1alpha1.Interface{} g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) - g.Expect(resource.Status.Conditions).To(HaveLen(3)) + g.Expect(resource.Status.Conditions).To(HaveLen(4)) 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.ConfiguredCondition)) g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionTrue)) g.Expect(resource.Status.Conditions[2].Type).To(Equal(v1alpha1.OperationalCondition)) g.Expect(resource.Status.Conditions[2].Status).To(Equal(metav1.ConditionTrue)) + g.Expect(resource.Status.Conditions[3].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[3].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) }) @@ -232,7 +236,7 @@ var _ = Describe("Interface Controller", func() { Eventually(func(g Gomega) { resource := &v1alpha1.Interface{} g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) - g.Expect(resource.Status.Conditions).To(HaveLen(3)) + g.Expect(resource.Status.Conditions).To(HaveLen(4)) 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[1].Type).To(Equal(v1alpha1.ConfiguredCondition)) @@ -240,6 +244,8 @@ var _ = Describe("Interface Controller", func() { g.Expect(resource.Status.Conditions[1].Reason).To(Equal(v1alpha1.CrossDeviceReferenceReason)) g.Expect(resource.Status.Conditions[2].Type).To(Equal(v1alpha1.OperationalCondition)) g.Expect(resource.Status.Conditions[2].Status).To(Equal(metav1.ConditionUnknown)) + g.Expect(resource.Status.Conditions[3].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[3].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) }) @@ -268,7 +274,7 @@ var _ = Describe("Interface Controller", func() { Eventually(func(g Gomega) { resource := &v1alpha1.Interface{} g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) - g.Expect(resource.Status.Conditions).To(HaveLen(3)) + g.Expect(resource.Status.Conditions).To(HaveLen(4)) 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[1].Type).To(Equal(v1alpha1.ConfiguredCondition)) @@ -276,6 +282,8 @@ var _ = Describe("Interface Controller", func() { g.Expect(resource.Status.Conditions[1].Reason).To(Equal(v1alpha1.InterfaceNotFoundReason)) g.Expect(resource.Status.Conditions[2].Type).To(Equal(v1alpha1.OperationalCondition)) g.Expect(resource.Status.Conditions[2].Status).To(Equal(metav1.ConditionUnknown)) + g.Expect(resource.Status.Conditions[3].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[3].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) }) @@ -341,13 +349,15 @@ var _ = Describe("Interface Controller", func() { Eventually(func(g Gomega) { resource := &v1alpha1.Interface{} g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) - g.Expect(resource.Status.Conditions).To(HaveLen(3)) + g.Expect(resource.Status.Conditions).To(HaveLen(4)) 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.ConfiguredCondition)) g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionTrue)) g.Expect(resource.Status.Conditions[2].Type).To(Equal(v1alpha1.OperationalCondition)) g.Expect(resource.Status.Conditions[2].Status).To(Equal(metav1.ConditionTrue)) + g.Expect(resource.Status.Conditions[3].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[3].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) By("Verifying member interfaces are properly linked") @@ -399,7 +409,7 @@ var _ = Describe("Interface Controller", func() { Eventually(func(g Gomega) { resource := &v1alpha1.Interface{} g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) - g.Expect(resource.Status.Conditions).To(HaveLen(3)) + g.Expect(resource.Status.Conditions).To(HaveLen(4)) 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[1].Type).To(Equal(v1alpha1.ConfiguredCondition)) @@ -407,6 +417,8 @@ var _ = Describe("Interface Controller", func() { g.Expect(resource.Status.Conditions[1].Reason).To(Equal(v1alpha1.InterfaceNotFoundReason)) g.Expect(resource.Status.Conditions[2].Type).To(Equal(v1alpha1.OperationalCondition)) g.Expect(resource.Status.Conditions[2].Status).To(Equal(metav1.ConditionUnknown)) + g.Expect(resource.Status.Conditions[3].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[3].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) }) @@ -453,7 +465,7 @@ var _ = Describe("Interface Controller", func() { Eventually(func(g Gomega) { resource := &v1alpha1.Interface{} g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) - g.Expect(resource.Status.Conditions).To(HaveLen(3)) + g.Expect(resource.Status.Conditions).To(HaveLen(4)) 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[1].Type).To(Equal(v1alpha1.ConfiguredCondition)) @@ -461,6 +473,8 @@ var _ = Describe("Interface Controller", func() { g.Expect(resource.Status.Conditions[1].Reason).To(Equal(v1alpha1.CrossDeviceReferenceReason)) g.Expect(resource.Status.Conditions[2].Type).To(Equal(v1alpha1.OperationalCondition)) g.Expect(resource.Status.Conditions[2].Status).To(Equal(metav1.ConditionUnknown)) + g.Expect(resource.Status.Conditions[3].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[3].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) }) @@ -512,7 +526,7 @@ var _ = Describe("Interface Controller", func() { Eventually(func(g Gomega) { resource := &v1alpha1.Interface{} g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) - g.Expect(resource.Status.Conditions).To(HaveLen(3)) + g.Expect(resource.Status.Conditions).To(HaveLen(4)) 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[1].Type).To(Equal(v1alpha1.ConfiguredCondition)) @@ -520,6 +534,8 @@ var _ = Describe("Interface Controller", func() { g.Expect(resource.Status.Conditions[1].Reason).To(Equal(v1alpha1.MemberInterfaceAlreadyInUseReason)) g.Expect(resource.Status.Conditions[2].Type).To(Equal(v1alpha1.OperationalCondition)) g.Expect(resource.Status.Conditions[2].Status).To(Equal(metav1.ConditionUnknown)) + g.Expect(resource.Status.Conditions[3].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[3].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) }) @@ -566,7 +582,7 @@ var _ = Describe("Interface Controller", func() { Eventually(func(g Gomega) { resource := &v1alpha1.Interface{} g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) - g.Expect(resource.Status.Conditions).To(HaveLen(3)) + g.Expect(resource.Status.Conditions).To(HaveLen(4)) 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[1].Type).To(Equal(v1alpha1.ConfiguredCondition)) @@ -574,6 +590,8 @@ var _ = Describe("Interface Controller", func() { g.Expect(resource.Status.Conditions[1].Reason).To(Equal(v1alpha1.InvalidInterfaceTypeReason)) g.Expect(resource.Status.Conditions[2].Type).To(Equal(v1alpha1.OperationalCondition)) g.Expect(resource.Status.Conditions[2].Status).To(Equal(metav1.ConditionUnknown)) + g.Expect(resource.Status.Conditions[3].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[3].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) }) @@ -620,7 +638,7 @@ var _ = Describe("Interface Controller", func() { Eventually(func(g Gomega) { resource := &v1alpha1.Interface{} g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) - g.Expect(resource.Status.Conditions).To(HaveLen(3)) + g.Expect(resource.Status.Conditions).To(HaveLen(4)) 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[1].Type).To(Equal(v1alpha1.ConfiguredCondition)) @@ -628,6 +646,8 @@ var _ = Describe("Interface Controller", func() { g.Expect(resource.Status.Conditions[1].Reason).To(Equal(v1alpha1.InvalidInterfaceTypeReason)) g.Expect(resource.Status.Conditions[2].Type).To(Equal(v1alpha1.OperationalCondition)) g.Expect(resource.Status.Conditions[2].Status).To(Equal(metav1.ConditionUnknown)) + g.Expect(resource.Status.Conditions[3].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[3].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) }) @@ -672,13 +692,15 @@ var _ = Describe("Interface Controller", func() { Eventually(func(g Gomega) { resource := &v1alpha1.Interface{} g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) - g.Expect(resource.Status.Conditions).To(HaveLen(3)) + g.Expect(resource.Status.Conditions).To(HaveLen(4)) 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.ConfiguredCondition)) g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionTrue)) g.Expect(resource.Status.Conditions[2].Type).To(Equal(v1alpha1.OperationalCondition)) g.Expect(resource.Status.Conditions[2].Status).To(Equal(metav1.ConditionTrue)) + g.Expect(resource.Status.Conditions[3].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[3].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) By("Verifying the VLAN status is updated with RoutedBy reference") @@ -719,7 +741,7 @@ var _ = Describe("Interface Controller", func() { Eventually(func(g Gomega) { resource := &v1alpha1.Interface{} g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) - g.Expect(resource.Status.Conditions).To(HaveLen(3)) + g.Expect(resource.Status.Conditions).To(HaveLen(4)) 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[1].Type).To(Equal(v1alpha1.ConfiguredCondition)) @@ -727,6 +749,8 @@ var _ = Describe("Interface Controller", func() { g.Expect(resource.Status.Conditions[1].Reason).To(Equal(v1alpha1.VLANNotFoundReason)) g.Expect(resource.Status.Conditions[2].Type).To(Equal(v1alpha1.OperationalCondition)) g.Expect(resource.Status.Conditions[2].Status).To(Equal(metav1.ConditionUnknown)) + g.Expect(resource.Status.Conditions[3].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[3].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) }) @@ -769,7 +793,7 @@ var _ = Describe("Interface Controller", func() { Eventually(func(g Gomega) { resource := &v1alpha1.Interface{} g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) - g.Expect(resource.Status.Conditions).To(HaveLen(3)) + g.Expect(resource.Status.Conditions).To(HaveLen(4)) 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[1].Type).To(Equal(v1alpha1.ConfiguredCondition)) @@ -777,6 +801,8 @@ var _ = Describe("Interface Controller", func() { g.Expect(resource.Status.Conditions[1].Reason).To(Equal(v1alpha1.CrossDeviceReferenceReason)) g.Expect(resource.Status.Conditions[2].Type).To(Equal(v1alpha1.OperationalCondition)) g.Expect(resource.Status.Conditions[2].Status).To(Equal(metav1.ConditionUnknown)) + g.Expect(resource.Status.Conditions[3].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[3].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) }) @@ -819,13 +845,15 @@ var _ = Describe("Interface Controller", func() { Eventually(func(g Gomega) { resource := &v1alpha1.Interface{} g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) - g.Expect(resource.Status.Conditions).To(HaveLen(3)) + g.Expect(resource.Status.Conditions).To(HaveLen(4)) 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.ConfiguredCondition)) g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionTrue)) g.Expect(resource.Status.Conditions[2].Type).To(Equal(v1alpha1.OperationalCondition)) g.Expect(resource.Status.Conditions[2].Status).To(Equal(metav1.ConditionTrue)) + g.Expect(resource.Status.Conditions[3].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[3].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) By("Verifying the Interface has the VRF label") @@ -865,7 +893,7 @@ var _ = Describe("Interface Controller", func() { Eventually(func(g Gomega) { resource := &v1alpha1.Interface{} g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) - g.Expect(resource.Status.Conditions).To(HaveLen(3)) + g.Expect(resource.Status.Conditions).To(HaveLen(4)) 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[1].Type).To(Equal(v1alpha1.ConfiguredCondition)) @@ -873,6 +901,8 @@ var _ = Describe("Interface Controller", func() { g.Expect(resource.Status.Conditions[1].Reason).To(Equal(v1alpha1.VRFNotFoundReason)) g.Expect(resource.Status.Conditions[2].Type).To(Equal(v1alpha1.OperationalCondition)) g.Expect(resource.Status.Conditions[2].Status).To(Equal(metav1.ConditionUnknown)) + g.Expect(resource.Status.Conditions[3].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[3].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) }) @@ -914,7 +944,7 @@ var _ = Describe("Interface Controller", func() { Eventually(func(g Gomega) { resource := &v1alpha1.Interface{} g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) - g.Expect(resource.Status.Conditions).To(HaveLen(3)) + g.Expect(resource.Status.Conditions).To(HaveLen(4)) 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[1].Type).To(Equal(v1alpha1.ConfiguredCondition)) @@ -922,6 +952,8 @@ var _ = Describe("Interface Controller", func() { g.Expect(resource.Status.Conditions[1].Reason).To(Equal(v1alpha1.CrossDeviceReferenceReason)) g.Expect(resource.Status.Conditions[2].Type).To(Equal(v1alpha1.OperationalCondition)) g.Expect(resource.Status.Conditions[2].Status).To(Equal(metav1.ConditionUnknown)) + g.Expect(resource.Status.Conditions[3].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[3].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) }) }) diff --git a/internal/controller/core/isis_controller.go b/internal/controller/core/isis_controller.go index 2be7bef1..8b0df54e 100644 --- a/internal/controller/core/isis_controller.go +++ b/internal/controller/core/isis_controller.go @@ -29,9 +29,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/ironcore-dev/network-operator/api/core/v1alpha1" - "github.com/ironcore-dev/network-operator/internal/annotations" "github.com/ironcore-dev/network-operator/internal/conditions" "github.com/ironcore-dev/network-operator/internal/deviceutil" + "github.com/ironcore-dev/network-operator/internal/paused" "github.com/ironcore-dev/network-operator/internal/provider" "github.com/ironcore-dev/network-operator/internal/resourcelock" ) @@ -107,9 +107,8 @@ func (r *ISISReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctr return ctrl.Result{}, err } - if annotations.IsPaused(device, obj) { - log.Info("Reconciliation is paused for this object") - return ctrl.Result{}, nil + if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { + return ctrl.Result{Requeue: requeue}, err } if err := r.Locker.AcquireLock(ctx, device.Name, "isis-controller"); err != nil { diff --git a/internal/controller/core/isis_controller_test.go b/internal/controller/core/isis_controller_test.go index 9ece97d1..3331cf3e 100644 --- a/internal/controller/core/isis_controller_test.go +++ b/internal/controller/core/isis_controller_test.go @@ -107,9 +107,11 @@ 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(1)) + g.Expect(resource.Status.Conditions).To(HaveLen(2)) 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)) }).Should(Succeed()) By("Ensuring the resource is created in the provider") diff --git a/internal/controller/core/lldp_controller.go b/internal/controller/core/lldp_controller.go index df25315c..56424786 100644 --- a/internal/controller/core/lldp_controller.go +++ b/internal/controller/core/lldp_controller.go @@ -31,9 +31,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/ironcore-dev/network-operator/api/core/v1alpha1" - "github.com/ironcore-dev/network-operator/internal/annotations" "github.com/ironcore-dev/network-operator/internal/conditions" "github.com/ironcore-dev/network-operator/internal/deviceutil" + "github.com/ironcore-dev/network-operator/internal/paused" "github.com/ironcore-dev/network-operator/internal/provider" "github.com/ironcore-dev/network-operator/internal/resourcelock" ) @@ -104,9 +104,8 @@ func (r *LLDPReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctr return ctrl.Result{}, err } - if annotations.IsPaused(device, obj) { - log.Info("Reconciliation is paused for this object") - return ctrl.Result{}, nil + if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { + return ctrl.Result{Requeue: requeue}, err } // Prevent concurrent reconciliations of resources targeting the same device diff --git a/internal/controller/core/lldp_controller_test.go b/internal/controller/core/lldp_controller_test.go index 68b2a40f..cf630b1d 100644 --- a/internal/controller/core/lldp_controller_test.go +++ b/internal/controller/core/lldp_controller_test.go @@ -111,7 +111,7 @@ var _ = Describe("LLDP Controller", func() { Eventually(func(g Gomega) { lldp = &v1alpha1.LLDP{} g.Expect(k8sClient.Get(ctx, resourceKey, lldp)).To(Succeed()) - g.Expect(lldp.Status.Conditions).To(HaveLen(3)) + g.Expect(lldp.Status.Conditions).To(HaveLen(4)) cond := meta.FindStatusCondition(lldp.Status.Conditions, v1alpha1.ReadyCondition) g.Expect(cond).ToNot(BeNil()) @@ -160,7 +160,7 @@ var _ = Describe("LLDP Controller", func() { Eventually(func(g Gomega) { lldp = &v1alpha1.LLDP{} g.Expect(k8sClient.Get(ctx, resourceKey, lldp)).To(Succeed()) - g.Expect(lldp.Status.Conditions).To(HaveLen(3)) + g.Expect(lldp.Status.Conditions).To(HaveLen(4)) cond := meta.FindStatusCondition(lldp.Status.Conditions, v1alpha1.ReadyCondition) g.Expect(cond).ToNot(BeNil()) diff --git a/internal/controller/core/managementaccess_controller.go b/internal/controller/core/managementaccess_controller.go index 78097eba..c9924be7 100644 --- a/internal/controller/core/managementaccess_controller.go +++ b/internal/controller/core/managementaccess_controller.go @@ -29,9 +29,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/ironcore-dev/network-operator/api/core/v1alpha1" - "github.com/ironcore-dev/network-operator/internal/annotations" "github.com/ironcore-dev/network-operator/internal/conditions" "github.com/ironcore-dev/network-operator/internal/deviceutil" + "github.com/ironcore-dev/network-operator/internal/paused" "github.com/ironcore-dev/network-operator/internal/provider" "github.com/ironcore-dev/network-operator/internal/resourcelock" ) @@ -103,9 +103,8 @@ func (r *ManagementAccessReconciler) Reconcile(ctx context.Context, req ctrl.Req return ctrl.Result{}, err } - if annotations.IsPaused(device, obj) { - log.Info("Reconciliation is paused for this object") - return ctrl.Result{}, nil + if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { + return ctrl.Result{Requeue: requeue}, err } if err := r.Locker.AcquireLock(ctx, device.Name, "managementaccess-controller"); err != nil { diff --git a/internal/controller/core/managementaccess_controller_test.go b/internal/controller/core/managementaccess_controller_test.go index ab0d7cb7..bc52d747 100644 --- a/internal/controller/core/managementaccess_controller_test.go +++ b/internal/controller/core/managementaccess_controller_test.go @@ -104,9 +104,11 @@ 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(1)) + g.Expect(resource.Status.Conditions).To(HaveLen(2)) 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)) }).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 ad2d29ff..d0a43417 100644 --- a/internal/controller/core/ntp_controller.go +++ b/internal/controller/core/ntp_controller.go @@ -29,9 +29,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/ironcore-dev/network-operator/api/core/v1alpha1" - "github.com/ironcore-dev/network-operator/internal/annotations" "github.com/ironcore-dev/network-operator/internal/conditions" "github.com/ironcore-dev/network-operator/internal/deviceutil" + "github.com/ironcore-dev/network-operator/internal/paused" "github.com/ironcore-dev/network-operator/internal/provider" "github.com/ironcore-dev/network-operator/internal/resourcelock" ) @@ -103,9 +103,8 @@ func (r *NTPReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl return ctrl.Result{}, err } - if annotations.IsPaused(device, obj) { - log.Info("Reconciliation is paused for this object") - return ctrl.Result{}, nil + if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { + return ctrl.Result{Requeue: requeue}, err } if err := r.Locker.AcquireLock(ctx, device.Name, "ntp-controller"); err != nil { diff --git a/internal/controller/core/ntp_controller_test.go b/internal/controller/core/ntp_controller_test.go index 4b21987a..c51dab2b 100644 --- a/internal/controller/core/ntp_controller_test.go +++ b/internal/controller/core/ntp_controller_test.go @@ -107,9 +107,11 @@ 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(1)) + g.Expect(resource.Status.Conditions).To(HaveLen(2)) 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)) }).Should(Succeed()) By("Ensuring the resource is created in the provider") diff --git a/internal/controller/core/nve_controller.go b/internal/controller/core/nve_controller.go index 42f02e28..e2f55f3d 100644 --- a/internal/controller/core/nve_controller.go +++ b/internal/controller/core/nve_controller.go @@ -31,9 +31,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/ironcore-dev/network-operator/api/core/v1alpha1" - "github.com/ironcore-dev/network-operator/internal/annotations" "github.com/ironcore-dev/network-operator/internal/conditions" "github.com/ironcore-dev/network-operator/internal/deviceutil" + "github.com/ironcore-dev/network-operator/internal/paused" "github.com/ironcore-dev/network-operator/internal/provider" "github.com/ironcore-dev/network-operator/internal/resourcelock" ) @@ -104,9 +104,8 @@ func (r *NetworkVirtualizationEdgeReconciler) Reconcile(ctx context.Context, req return ctrl.Result{}, err } - if annotations.IsPaused(device, obj) { - log.Info("Reconciliation is paused for this object") - return ctrl.Result{}, nil + if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { + return ctrl.Result{Requeue: requeue}, err } if err := r.Locker.AcquireLock(ctx, device.Name, "nve-controller"); err != nil { diff --git a/internal/controller/core/nve_controller_test.go b/internal/controller/core/nve_controller_test.go index cf77197b..0ce53e62 100644 --- a/internal/controller/core/nve_controller_test.go +++ b/internal/controller/core/nve_controller_test.go @@ -135,13 +135,15 @@ var _ = Describe("NVE Controller", func() { By("Updating the resource status") Eventually(func(g Gomega) { g.Expect(k8sClient.Get(ctx, nveKey, nve)).To(Succeed()) - g.Expect(nve.Status.Conditions).To(HaveLen(3)) + g.Expect(nve.Status.Conditions).To(HaveLen(4)) g.Expect(nve.Status.Conditions[0].Type).To(Equal(v1alpha1.ReadyCondition)) g.Expect(nve.Status.Conditions[0].Status).To(Equal(metav1.ConditionTrue)) g.Expect(nve.Status.Conditions[1].Type).To(Equal(v1alpha1.ConfiguredCondition)) g.Expect(nve.Status.Conditions[1].Status).To(Equal(metav1.ConditionTrue)) g.Expect(nve.Status.Conditions[2].Type).To(Equal(v1alpha1.OperationalCondition)) g.Expect(nve.Status.Conditions[2].Status).To(Equal(metav1.ConditionTrue)) + g.Expect(nve.Status.Conditions[3].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(nve.Status.Conditions[3].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) By("Ensuring the NVE is created in the provider") @@ -173,13 +175,15 @@ var _ = Describe("NVE Controller", func() { Eventually(func(g Gomega) { resource := &v1alpha1.NetworkVirtualizationEdge{} g.Expect(k8sClient.Get(ctx, nveKey, resource)).To(Succeed()) - g.Expect(resource.Status.Conditions).To(HaveLen(3)) + g.Expect(resource.Status.Conditions).To(HaveLen(4)) 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.ConfiguredCondition)) g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionTrue)) g.Expect(resource.Status.Conditions[2].Type).To(Equal(v1alpha1.OperationalCondition)) g.Expect(resource.Status.Conditions[2].Status).To(Equal(metav1.ConditionTrue)) + g.Expect(resource.Status.Conditions[3].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[3].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) }) }) diff --git a/internal/controller/core/ospf_controller.go b/internal/controller/core/ospf_controller.go index 54ef5398..86e70c6b 100644 --- a/internal/controller/core/ospf_controller.go +++ b/internal/controller/core/ospf_controller.go @@ -32,9 +32,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/ironcore-dev/network-operator/api/core/v1alpha1" - "github.com/ironcore-dev/network-operator/internal/annotations" "github.com/ironcore-dev/network-operator/internal/conditions" "github.com/ironcore-dev/network-operator/internal/deviceutil" + "github.com/ironcore-dev/network-operator/internal/paused" "github.com/ironcore-dev/network-operator/internal/provider" "github.com/ironcore-dev/network-operator/internal/resourcelock" ) @@ -110,9 +110,8 @@ func (r *OSPFReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctr return ctrl.Result{}, err } - if annotations.IsPaused(device, obj) { - log.Info("Reconciliation is paused for this object") - return ctrl.Result{}, nil + if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { + return ctrl.Result{Requeue: requeue}, err } if err := r.Locker.AcquireLock(ctx, device.Name, "ospf-controller"); err != nil { diff --git a/internal/controller/core/ospf_controller_test.go b/internal/controller/core/ospf_controller_test.go index 423e33a4..18399169 100644 --- a/internal/controller/core/ospf_controller_test.go +++ b/internal/controller/core/ospf_controller_test.go @@ -102,13 +102,15 @@ var _ = Describe("OSPF Controller", func() { Eventually(func(g Gomega) { resource := &v1alpha1.OSPF{} g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) - g.Expect(resource.Status.Conditions).To(HaveLen(3)) + g.Expect(resource.Status.Conditions).To(HaveLen(4)) 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.ConfiguredCondition)) g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionTrue)) g.Expect(resource.Status.Conditions[2].Type).To(Equal(v1alpha1.OperationalCondition)) g.Expect(resource.Status.Conditions[2].Status).To(Equal(metav1.ConditionTrue)) + g.Expect(resource.Status.Conditions[3].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[3].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 141b08f6..b444b288 100644 --- a/internal/controller/core/pim_controller.go +++ b/internal/controller/core/pim_controller.go @@ -29,9 +29,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/ironcore-dev/network-operator/api/core/v1alpha1" - "github.com/ironcore-dev/network-operator/internal/annotations" "github.com/ironcore-dev/network-operator/internal/conditions" "github.com/ironcore-dev/network-operator/internal/deviceutil" + "github.com/ironcore-dev/network-operator/internal/paused" "github.com/ironcore-dev/network-operator/internal/provider" "github.com/ironcore-dev/network-operator/internal/resourcelock" ) @@ -107,9 +107,8 @@ func (r *PIMReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl return ctrl.Result{}, err } - if annotations.IsPaused(device, obj) { - log.Info("Reconciliation is paused for this object") - return ctrl.Result{}, nil + if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { + return ctrl.Result{Requeue: requeue}, err } if err := r.Locker.AcquireLock(ctx, device.Name, "pim-controller"); err != nil { diff --git a/internal/controller/core/pim_controller_test.go b/internal/controller/core/pim_controller_test.go index f419f959..7db0d762 100644 --- a/internal/controller/core/pim_controller_test.go +++ b/internal/controller/core/pim_controller_test.go @@ -100,9 +100,11 @@ 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(1)) + g.Expect(resource.Status.Conditions).To(HaveLen(2)) 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)) }).Should(Succeed()) By("Ensuring the resource is created in the provider") diff --git a/internal/controller/core/prefixset_controller.go b/internal/controller/core/prefixset_controller.go index 23341a68..9ee8299c 100644 --- a/internal/controller/core/prefixset_controller.go +++ b/internal/controller/core/prefixset_controller.go @@ -29,9 +29,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/ironcore-dev/network-operator/api/core/v1alpha1" - "github.com/ironcore-dev/network-operator/internal/annotations" "github.com/ironcore-dev/network-operator/internal/conditions" "github.com/ironcore-dev/network-operator/internal/deviceutil" + "github.com/ironcore-dev/network-operator/internal/paused" "github.com/ironcore-dev/network-operator/internal/provider" "github.com/ironcore-dev/network-operator/internal/resourcelock" ) @@ -103,9 +103,8 @@ func (r *PrefixSetReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( return ctrl.Result{}, err } - if annotations.IsPaused(device, obj) { - log.Info("Reconciliation is paused for this object") - return ctrl.Result{}, nil + if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { + return ctrl.Result{Requeue: requeue}, err } if err := r.Locker.AcquireLock(ctx, device.Name, "prefixset-controller"); err != nil { diff --git a/internal/controller/core/prefixset_controller_test.go b/internal/controller/core/prefixset_controller_test.go index 1dc946c6..5560d2af 100644 --- a/internal/controller/core/prefixset_controller_test.go +++ b/internal/controller/core/prefixset_controller_test.go @@ -111,9 +111,11 @@ 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(1)) + g.Expect(resource.Status.Conditions).To(HaveLen(2)) 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)) }).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 49c2028d..6faa037a 100644 --- a/internal/controller/core/routingpolicy_controller.go +++ b/internal/controller/core/routingpolicy_controller.go @@ -29,9 +29,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/ironcore-dev/network-operator/api/core/v1alpha1" - "github.com/ironcore-dev/network-operator/internal/annotations" "github.com/ironcore-dev/network-operator/internal/conditions" "github.com/ironcore-dev/network-operator/internal/deviceutil" + "github.com/ironcore-dev/network-operator/internal/paused" "github.com/ironcore-dev/network-operator/internal/provider" "github.com/ironcore-dev/network-operator/internal/resourcelock" ) @@ -103,9 +103,8 @@ func (r *RoutingPolicyReconciler) Reconcile(ctx context.Context, req ctrl.Reques return ctrl.Result{}, err } - if annotations.IsPaused(device, obj) { - log.Info("Reconciliation is paused for this object") - return ctrl.Result{}, nil + if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { + return ctrl.Result{Requeue: requeue}, err } if err := r.Locker.AcquireLock(ctx, device.Name, "routingpolicy-controller"); err != nil { diff --git a/internal/controller/core/routingpolicy_controller_test.go b/internal/controller/core/routingpolicy_controller_test.go index a4d25465..11f387b5 100644 --- a/internal/controller/core/routingpolicy_controller_test.go +++ b/internal/controller/core/routingpolicy_controller_test.go @@ -115,9 +115,11 @@ 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(1)) + g.Expect(resource.Status.Conditions).To(HaveLen(2)) 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)) }).Should(Succeed()) By("Ensuring the resource is created in the provider") @@ -184,9 +186,11 @@ 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(1)) + g.Expect(resource.Status.Conditions).To(HaveLen(2)) 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)) }).Should(Succeed()) By("Verifying the RoutingPolicy is configured in the provider") @@ -226,10 +230,12 @@ 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(1)) + g.Expect(resource.Status.Conditions).To(HaveLen(2)) 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].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) }) @@ -283,10 +289,12 @@ 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(1)) + g.Expect(resource.Status.Conditions).To(HaveLen(2)) 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].Status).To(Equal(metav1.ConditionFalse)) }).Should(Succeed()) }) }) diff --git a/internal/controller/core/snmp_controller.go b/internal/controller/core/snmp_controller.go index 4583de4b..f01fbf3f 100644 --- a/internal/controller/core/snmp_controller.go +++ b/internal/controller/core/snmp_controller.go @@ -29,9 +29,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/ironcore-dev/network-operator/api/core/v1alpha1" - "github.com/ironcore-dev/network-operator/internal/annotations" "github.com/ironcore-dev/network-operator/internal/conditions" "github.com/ironcore-dev/network-operator/internal/deviceutil" + "github.com/ironcore-dev/network-operator/internal/paused" "github.com/ironcore-dev/network-operator/internal/provider" "github.com/ironcore-dev/network-operator/internal/resourcelock" ) @@ -103,9 +103,8 @@ func (r *SNMPReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctr return ctrl.Result{}, err } - if annotations.IsPaused(device, obj) { - log.Info("Reconciliation is paused for this object") - return ctrl.Result{}, nil + if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { + return ctrl.Result{Requeue: requeue}, err } if err := r.Locker.AcquireLock(ctx, device.Name, "snmp-controller"); err != nil { diff --git a/internal/controller/core/snmp_controller_test.go b/internal/controller/core/snmp_controller_test.go index b29ccfdb..8a32f1f4 100644 --- a/internal/controller/core/snmp_controller_test.go +++ b/internal/controller/core/snmp_controller_test.go @@ -118,9 +118,11 @@ 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(1)) + g.Expect(resource.Status.Conditions).To(HaveLen(2)) 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)) }).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 5ce5460a..44baaead 100644 --- a/internal/controller/core/syslog_controller.go +++ b/internal/controller/core/syslog_controller.go @@ -29,9 +29,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/ironcore-dev/network-operator/api/core/v1alpha1" - "github.com/ironcore-dev/network-operator/internal/annotations" "github.com/ironcore-dev/network-operator/internal/conditions" "github.com/ironcore-dev/network-operator/internal/deviceutil" + "github.com/ironcore-dev/network-operator/internal/paused" "github.com/ironcore-dev/network-operator/internal/provider" "github.com/ironcore-dev/network-operator/internal/resourcelock" ) @@ -103,9 +103,8 @@ func (r *SyslogReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ c return ctrl.Result{}, err } - if annotations.IsPaused(device, obj) { - log.Info("Reconciliation is paused for this object") - return ctrl.Result{}, nil + if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { + return ctrl.Result{Requeue: requeue}, err } if err := r.Locker.AcquireLock(ctx, device.Name, "syslog-controller"); err != nil { diff --git a/internal/controller/core/syslog_controller_test.go b/internal/controller/core/syslog_controller_test.go index 700fabbf..fc16816b 100644 --- a/internal/controller/core/syslog_controller_test.go +++ b/internal/controller/core/syslog_controller_test.go @@ -113,9 +113,11 @@ 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(1)) + g.Expect(resource.Status.Conditions).To(HaveLen(2)) 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)) }).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 f963eb35..81295fd3 100644 --- a/internal/controller/core/user_controller.go +++ b/internal/controller/core/user_controller.go @@ -30,10 +30,10 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/ironcore-dev/network-operator/api/core/v1alpha1" - "github.com/ironcore-dev/network-operator/internal/annotations" "github.com/ironcore-dev/network-operator/internal/clientutil" "github.com/ironcore-dev/network-operator/internal/conditions" "github.com/ironcore-dev/network-operator/internal/deviceutil" + "github.com/ironcore-dev/network-operator/internal/paused" "github.com/ironcore-dev/network-operator/internal/provider" "github.com/ironcore-dev/network-operator/internal/resourcelock" ) @@ -106,9 +106,8 @@ func (r *UserReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctr return ctrl.Result{}, err } - if annotations.IsPaused(device, obj) { - log.Info("Reconciliation is paused for this object") - return ctrl.Result{}, nil + if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { + return ctrl.Result{Requeue: requeue}, err } if err := r.Locker.AcquireLock(ctx, device.Name, "user-controller"); err != nil { diff --git a/internal/controller/core/user_controller_test.go b/internal/controller/core/user_controller_test.go index 20d6bee6..308660ea 100644 --- a/internal/controller/core/user_controller_test.go +++ b/internal/controller/core/user_controller_test.go @@ -123,9 +123,11 @@ 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(1)) + g.Expect(resource.Status.Conditions).To(HaveLen(2)) 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)) }).Should(Succeed()) By("Ensuring the resource is created in the provider") diff --git a/internal/controller/core/vlan_controller.go b/internal/controller/core/vlan_controller.go index 091a7b71..9fbbec75 100644 --- a/internal/controller/core/vlan_controller.go +++ b/internal/controller/core/vlan_controller.go @@ -29,9 +29,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/ironcore-dev/network-operator/api/core/v1alpha1" - "github.com/ironcore-dev/network-operator/internal/annotations" "github.com/ironcore-dev/network-operator/internal/conditions" "github.com/ironcore-dev/network-operator/internal/deviceutil" + "github.com/ironcore-dev/network-operator/internal/paused" "github.com/ironcore-dev/network-operator/internal/provider" "github.com/ironcore-dev/network-operator/internal/resourcelock" ) @@ -107,9 +107,8 @@ func (r *VLANReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctr return ctrl.Result{}, err } - if annotations.IsPaused(device, obj) { - log.Info("Reconciliation is paused for this object") - return ctrl.Result{}, nil + if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { + return ctrl.Result{Requeue: requeue}, err } if err := r.Locker.AcquireLock(ctx, device.Name, "vlan-controller"); err != nil { diff --git a/internal/controller/core/vlan_controller_test.go b/internal/controller/core/vlan_controller_test.go index b635265d..51a078a4 100644 --- a/internal/controller/core/vlan_controller_test.go +++ b/internal/controller/core/vlan_controller_test.go @@ -103,13 +103,15 @@ var _ = Describe("VLAN Controller", func() { Eventually(func(g Gomega) { resource := &v1alpha1.VLAN{} g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed()) - g.Expect(resource.Status.Conditions).To(HaveLen(3)) + g.Expect(resource.Status.Conditions).To(HaveLen(4)) 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.ConfiguredCondition)) g.Expect(resource.Status.Conditions[1].Status).To(Equal(metav1.ConditionTrue)) g.Expect(resource.Status.Conditions[2].Type).To(Equal(v1alpha1.OperationalCondition)) g.Expect(resource.Status.Conditions[2].Status).To(Equal(metav1.ConditionTrue)) + g.Expect(resource.Status.Conditions[3].Type).To(Equal(v1alpha1.PausedCondition)) + g.Expect(resource.Status.Conditions[3].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 2914f63a..8d763d96 100644 --- a/internal/controller/core/vrf_controller.go +++ b/internal/controller/core/vrf_controller.go @@ -29,9 +29,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/ironcore-dev/network-operator/api/core/v1alpha1" - "github.com/ironcore-dev/network-operator/internal/annotations" "github.com/ironcore-dev/network-operator/internal/conditions" "github.com/ironcore-dev/network-operator/internal/deviceutil" + "github.com/ironcore-dev/network-operator/internal/paused" "github.com/ironcore-dev/network-operator/internal/provider" "github.com/ironcore-dev/network-operator/internal/resourcelock" ) @@ -109,9 +109,8 @@ func (r *VRFReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl return ctrl.Result{}, err } - if annotations.IsPaused(device, obj) { - log.Info("Reconciliation is paused for this object") - return ctrl.Result{}, nil + if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { + return ctrl.Result{Requeue: requeue}, err } if err := r.Locker.AcquireLock(ctx, device.Name, "vrf-controller"); err != nil { diff --git a/internal/controller/core/vrf_controller_test.go b/internal/controller/core/vrf_controller_test.go index 4e208779..58ecb862 100644 --- a/internal/controller/core/vrf_controller_test.go +++ b/internal/controller/core/vrf_controller_test.go @@ -115,9 +115,11 @@ 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(1)) + g.Expect(vrf.Status.Conditions).To(HaveLen(2)) 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)) }).Should(Succeed()) By("Ensuring the VRF is created in the provider") diff --git a/internal/paused/paused.go b/internal/paused/paused.go new file mode 100644 index 00000000..2954d429 --- /dev/null +++ b/internal/paused/paused.go @@ -0,0 +1,96 @@ +// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and IronCore contributors +// SPDX-License-Identifier: Apache-2.0 + +// Package paused implements helper functions for managing the Paused condition on API objects. +package paused + +import ( + "context" + "fmt" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/ironcore-dev/network-operator/api/core/v1alpha1" + "github.com/ironcore-dev/network-operator/internal/conditions" +) + +// Object combines [client.Object] with [conditions.Setter]. +type Object interface { + client.Object + conditions.Setter +} + +// EnsureCondition computes and patches the "Paused" condition on the object. +// It returns whether the object is paused, whether the caller should requeue, +// and any error encountered while patching. +func EnsureCondition(ctx context.Context, c client.Client, device *v1alpha1.Device, obj Object) (isPaused, requeue bool, err error) { + log := ctrl.LoggerFrom(ctx) + + oldCondition := conditions.Get(obj, v1alpha1.PausedCondition) + newCondition := computeCondition(device, obj) + + isPaused = newCondition.Status == metav1.ConditionTrue + statusChanged := oldCondition == nil || oldCondition.Status != newCondition.Status + + switch { + case statusChanged && isPaused: + log.Info("Pausing reconciliation for this object", "reason", newCondition.Message) + case statusChanged && !isPaused: + log.Info("Unpausing reconciliation for this object") + case !statusChanged && isPaused: + log.V(4).Info("Reconciliation is paused for this object", "reason", newCondition.Message) + } + + // Set Ready=Unknown while paused: the operator is no longer actively + // verifying the resource, so its state cannot be determined. + if isPaused { + conditions.Set(obj, metav1.Condition{ + Type: v1alpha1.ReadyCondition, + Status: metav1.ConditionUnknown, + Reason: v1alpha1.PausedReason, + Message: "Reconciliation is paused", + }) + } + + // Only do a standalone status patch when pausing. When not paused, + // the condition is set in-memory and will be persisted by the normal + // reconciliation status update, avoiding an unnecessary extra reconcile. + orig := obj.DeepCopyObject().(client.Object) + if changed := conditions.Set(obj, newCondition); !changed || !isPaused { + return isPaused, false, nil + } + + if err = c.Status().Patch(ctx, obj, client.MergeFrom(orig)); err != nil { + return isPaused, false, err + } + + return isPaused, true, nil +} + +// computeCondition builds the Paused condition based on [v1alpha1.Device.Spec.Paused] +// and the presence of the [v1alpha1.PausedAnnotation] on the object. +func computeCondition(device *v1alpha1.Device, obj Object) metav1.Condition { + condition := metav1.Condition{ + Type: v1alpha1.PausedCondition, + Status: metav1.ConditionFalse, + Reason: v1alpha1.NotPausedReason, + ObservedGeneration: obj.GetGeneration(), + } + + if device != nil && device.Spec.Paused { + condition.Status = metav1.ConditionTrue + condition.Reason = v1alpha1.PausedReason + condition.Message = "Device spec.paused is set to true" + return condition + } + + if _, ok := obj.GetAnnotations()[v1alpha1.PausedAnnotation]; ok { + condition.Status = metav1.ConditionTrue + condition.Reason = v1alpha1.PausedReason + condition.Message = fmt.Sprintf("%s has the %s annotation", obj.GetObjectKind().GroupVersionKind().Kind, v1alpha1.PausedAnnotation) + } + + return condition +}