Skip to content

Commit 05a1cca

Browse files
Add pause reconciliation support for Device and associated resources
Introduce the ability to pause reconciliation for Devices and their associated resources. This is useful for maintenance scenarios where network device configuration should be temporarily frozen. Pausing can be enabled via: - Setting `spec.paused: true` on a Device (pauses Device and all associated resources) - Adding the `networking.metal.ironcore.dev/paused` annotation to any individual resource When paused, controllers will skip reconciliation and log an info message indicating the resource is paused.
1 parent 36478b9 commit 05a1cca

28 files changed

Lines changed: 188 additions & 4 deletions

api/core/v1alpha1/device_types.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ import (
1313

1414
// DeviceSpec defines the desired state of Device.
1515
type DeviceSpec struct {
16+
// Paused can be used to prevent controllers from processing the Device and its associated objects.
17+
// +optional
18+
Paused *bool `json:"paused,omitempty"`
19+
1620
// Endpoint contains the connection information for the device.
1721
// +required
1822
Endpoint Endpoint `json:"endpoint"`

api/core/v1alpha1/groupversion_info.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ var (
2525
// with reconciliation of the object only if this label and a configured value is present.
2626
const WatchLabel = "networking.metal.ironcore.dev/watch-filter"
2727

28+
// PausedAnnotation is an annotation that can be applied to any Network API object
29+
// to prevent a controller from processing it. Controllers working with Network API objects
30+
// must check the existence of this annotation on the reconciled object.
31+
const PausedAnnotation = "networking.metal.ironcore.dev/paused"
32+
2833
// FinalizerName is the identifier used by the controllers to perform cleanup before a resource is deleted.
2934
// It is added when the resource is created and ensures that the controller can handle teardown logic
3035
// (e.g., deleting external dependencies) before Kubernetes finalizes the deletion.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and IronCore contributors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
// Package annotations implements annotation helper functions.
5+
package annotations
6+
7+
import (
8+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
9+
10+
"github.com/ironcore-dev/network-operator/api/core/v1alpha1"
11+
)
12+
13+
// IsPaused returns true if the Device is paused or the object has the [v1alpha1.PausedAnnotation].
14+
func IsPaused(device *v1alpha1.Device, obj metav1.Object) bool {
15+
return (device.Spec.Paused != nil && *device.Spec.Paused) || HasPaused(obj)
16+
}
17+
18+
// HasPaused returns true if the object has the [v1alpha1.PausedAnnotation].
19+
func HasPaused(obj metav1.Object) bool {
20+
return Has(obj, v1alpha1.PausedAnnotation)
21+
}
22+
23+
// Has returns true if the object has the specified annotation.
24+
func Has(obj metav1.Object, annotation string) bool {
25+
_, ok := obj.GetAnnotations()[annotation]
26+
return ok
27+
}

internal/controller/cisco/nx/bordergateway_controller.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929

3030
nxv1alpha1 "github.com/ironcore-dev/network-operator/api/cisco/nx/v1alpha1"
3131
"github.com/ironcore-dev/network-operator/api/core/v1alpha1"
32+
"github.com/ironcore-dev/network-operator/internal/annotations"
3233
"github.com/ironcore-dev/network-operator/internal/conditions"
3334
"github.com/ironcore-dev/network-operator/internal/deviceutil"
3435
"github.com/ironcore-dev/network-operator/internal/provider"
@@ -103,6 +104,11 @@ func (r *BorderGatewayReconciler) Reconcile(ctx context.Context, req ctrl.Reques
103104
return ctrl.Result{}, err
104105
}
105106

107+
if annotations.IsPaused(device, obj) {
108+
log.Info("Reconciliation is paused for this object")
109+
return ctrl.Result{}, nil
110+
}
111+
106112
if err := r.Locker.AcquireLock(ctx, device.Name, "cisco-nx-border-gateway-controller"); err != nil {
107113
if errors.Is(err, resourcelock.ErrLockAlreadyHeld) {
108114
log.Info("Device is already locked, requeuing reconciliation")

internal/controller/cisco/nx/system_controller.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323

2424
nxv1alpha1 "github.com/ironcore-dev/network-operator/api/cisco/nx/v1alpha1"
2525
"github.com/ironcore-dev/network-operator/api/core/v1alpha1"
26+
"github.com/ironcore-dev/network-operator/internal/annotations"
2627
"github.com/ironcore-dev/network-operator/internal/conditions"
2728
"github.com/ironcore-dev/network-operator/internal/deviceutil"
2829
"github.com/ironcore-dev/network-operator/internal/provider"
@@ -96,6 +97,11 @@ func (r *SystemReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ c
9697
return ctrl.Result{}, err
9798
}
9899

100+
if annotations.IsPaused(device, obj) {
101+
log.Info("Reconciliation is paused for this object")
102+
return ctrl.Result{}, nil
103+
}
104+
99105
if err := r.Locker.AcquireLock(ctx, device.Name, "cisco-nx-system-controller"); err != nil {
100106
if errors.Is(err, resourcelock.ErrLockAlreadyHeld) {
101107
log.Info("Device is already locked, requeuing reconciliation")

internal/controller/cisco/nx/vpcdomain_controller.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"sigs.k8s.io/controller-runtime/pkg/predicate"
2828
"sigs.k8s.io/controller-runtime/pkg/reconcile"
2929

30+
"github.com/ironcore-dev/network-operator/internal/annotations"
3031
"github.com/ironcore-dev/network-operator/internal/conditions"
3132
"github.com/ironcore-dev/network-operator/internal/provider"
3233
"github.com/ironcore-dev/network-operator/internal/resourcelock"
@@ -112,6 +113,11 @@ func (r *VPCDomainReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
112113
return ctrl.Result{}, err
113114
}
114115

116+
if annotations.IsPaused(device, obj) {
117+
log.Info("Reconciliation is paused for this object")
118+
return ctrl.Result{}, nil
119+
}
120+
115121
if err := r.Locker.AcquireLock(ctx, device.Name, "cisco-nx-vpcdomain-controller"); err != nil {
116122
if errors.Is(err, resourcelock.ErrLockAlreadyHeld) {
117123
log.Info("Device is already locked, requeuing reconciliation")

internal/controller/core/acl_controller.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"sigs.k8s.io/controller-runtime/pkg/predicate"
2323

2424
"github.com/ironcore-dev/network-operator/api/core/v1alpha1"
25+
"github.com/ironcore-dev/network-operator/internal/annotations"
2526
"github.com/ironcore-dev/network-operator/internal/conditions"
2627
"github.com/ironcore-dev/network-operator/internal/deviceutil"
2728
"github.com/ironcore-dev/network-operator/internal/provider"
@@ -95,6 +96,11 @@ func (r *AccessControlListReconciler) Reconcile(ctx context.Context, req ctrl.Re
9596
return ctrl.Result{}, err
9697
}
9798

99+
if annotations.IsPaused(device, obj) {
100+
log.Info("Reconciliation is paused for this object")
101+
return ctrl.Result{}, nil
102+
}
103+
98104
if err := r.Locker.AcquireLock(ctx, device.Name, "acl-controller"); err != nil {
99105
if errors.Is(err, resourcelock.ErrLockAlreadyHeld) {
100106
log.Info("Device is already locked, requeuing reconciliation")

internal/controller/core/banner_controller.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"sigs.k8s.io/controller-runtime/pkg/predicate"
2727

2828
"github.com/ironcore-dev/network-operator/api/core/v1alpha1"
29+
"github.com/ironcore-dev/network-operator/internal/annotations"
2930
"github.com/ironcore-dev/network-operator/internal/clientutil"
3031
"github.com/ironcore-dev/network-operator/internal/conditions"
3132
"github.com/ironcore-dev/network-operator/internal/deviceutil"
@@ -102,6 +103,11 @@ func (r *BannerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ c
102103
return ctrl.Result{}, err
103104
}
104105

106+
if annotations.IsPaused(device, obj) {
107+
log.Info("Reconciliation is paused for this object")
108+
return ctrl.Result{}, nil
109+
}
110+
105111
if err := r.Locker.AcquireLock(ctx, device.Name, "banner-controller"); err != nil {
106112
if errors.Is(err, resourcelock.ErrLockAlreadyHeld) {
107113
log.Info("Device is already locked, requeuing reconciliation")

internal/controller/core/bgp_controller.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"sigs.k8s.io/controller-runtime/pkg/predicate"
2323

2424
"github.com/ironcore-dev/network-operator/api/core/v1alpha1"
25+
"github.com/ironcore-dev/network-operator/internal/annotations"
2526
"github.com/ironcore-dev/network-operator/internal/conditions"
2627
"github.com/ironcore-dev/network-operator/internal/deviceutil"
2728
"github.com/ironcore-dev/network-operator/internal/provider"
@@ -99,6 +100,11 @@ func (r *BGPReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl
99100
return ctrl.Result{}, err
100101
}
101102

103+
if annotations.IsPaused(device, obj) {
104+
log.Info("Reconciliation is paused for this object")
105+
return ctrl.Result{}, nil
106+
}
107+
102108
if err := r.Locker.AcquireLock(ctx, device.Name, "bgp-controller"); err != nil {
103109
if errors.Is(err, resourcelock.ErrLockAlreadyHeld) {
104110
log.Info("Device is already locked, requeuing reconciliation")

internal/controller/core/bgp_peer_controller.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"sigs.k8s.io/controller-runtime/pkg/reconcile"
2828

2929
"github.com/ironcore-dev/network-operator/api/core/v1alpha1"
30+
"github.com/ironcore-dev/network-operator/internal/annotations"
3031
"github.com/ironcore-dev/network-operator/internal/conditions"
3132
"github.com/ironcore-dev/network-operator/internal/deviceutil"
3233
"github.com/ironcore-dev/network-operator/internal/provider"
@@ -104,6 +105,11 @@ func (r *BGPPeerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_
104105
return ctrl.Result{}, err
105106
}
106107

108+
if annotations.IsPaused(device, obj) {
109+
log.Info("Reconciliation is paused for this object")
110+
return ctrl.Result{}, nil
111+
}
112+
107113
if err := r.Locker.AcquireLock(ctx, device.Name, "bgppeer-controller"); err != nil {
108114
if errors.Is(err, resourcelock.ErrLockAlreadyHeld) {
109115
log.Info("Device is already locked, requeuing reconciliation")

0 commit comments

Comments
 (0)