Skip to content

Commit ea1333c

Browse files
author
Peter Goron
committed
nodedisruption: do not bubble up NotFound error on status update
When a NodeDisruption disappears between Reconcile's Get and the final Status().Update (e.g. our finalizer was removed and the object was deleted from etcd), a NotFound error can be reported by nd reconciler, triggering false positive alerts.
1 parent 54e9ce9 commit ea1333c

2 files changed

Lines changed: 68 additions & 1 deletion

File tree

internal/controller/nodedisruption_controller.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,14 @@ func (r *NodeDisruptionReconciler) Reconcile(ctx context.Context, req ctrl.Reque
124124

125125
if !reflect.DeepEqual(nd.Status, reconciler.NodeDisruption.Status) {
126126
logger.Info("Updating Status, done with", "state", reconciler.NodeDisruption.Status.State)
127-
return clusterResult, reconciler.UpdateStatus(ctx)
127+
if err := reconciler.UpdateStatus(ctx); err != nil {
128+
if errors.IsNotFound(err) {
129+
logger.Info("NodeDisruption was deleted before status update, skipping", "error", err)
130+
return clusterResult, nil
131+
}
132+
return clusterResult, err
133+
}
134+
return clusterResult, nil
128135
}
129136
logger.Info("Reconciliation successful", "state", reconciler.NodeDisruption.Status.State)
130137
return clusterResult, nil

internal/controller/nodedisruption_controller_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,20 +23,26 @@ import (
2323
"io"
2424
"net/http"
2525
"net/http/httptest"
26+
"testing"
2627
"time"
2728

2829
nodedisruptionv1alpha1 "github.com/criteo/node-disruption-controller/api/v1alpha1"
2930
"github.com/criteo/node-disruption-controller/internal/appmgr"
3031
. "github.com/onsi/ginkgo/v2"
3132
. "github.com/onsi/gomega"
33+
"github.com/stretchr/testify/assert"
34+
"github.com/stretchr/testify/require"
3235
corev1 "k8s.io/api/core/v1"
3336
errorsk8s "k8s.io/apimachinery/pkg/api/errors"
3437
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
38+
"k8s.io/apimachinery/pkg/runtime"
3539
"k8s.io/apimachinery/pkg/types"
3640
"k8s.io/client-go/kubernetes/scheme"
3741

3842
ctrl "sigs.k8s.io/controller-runtime"
3943
"sigs.k8s.io/controller-runtime/pkg/client"
44+
"sigs.k8s.io/controller-runtime/pkg/client/fake"
45+
"sigs.k8s.io/controller-runtime/pkg/client/interceptor"
4046
)
4147

4248
// +kubebuilder:docs-gen:collapse=Imports
@@ -1159,3 +1165,57 @@ var _ = Describe("NodeDisruption controller", func() {
11591165
})
11601166
})
11611167
})
1168+
1169+
// TestReconcile_DoesNotErrorOnNotFoundStatusUpdate verifies that when the
1170+
// NodeDisruption is removed between Reconcile's Get and the status update
1171+
// (e.g., the object's finalizer was removed and it was deleted from etcd),
1172+
// Reconcile swallows the resulting NotFound error instead of bubbling it up to
1173+
// the controller-runtime workqueue (which would log it as a Reconciler error
1174+
// and trigger an unnecessary requeue).
1175+
func TestReconcile_DoesNotErrorOnNotFoundStatusUpdate(t *testing.T) {
1176+
s := runtime.NewScheme()
1177+
require.NoError(t, scheme.AddToScheme(s))
1178+
require.NoError(t, nodedisruptionv1alpha1.AddToScheme(s))
1179+
1180+
nd := &nodedisruptionv1alpha1.NodeDisruption{
1181+
ObjectMeta: metav1.ObjectMeta{
1182+
Name: "vanishing-nd",
1183+
Namespace: "default",
1184+
Finalizers: []string{FinalizerName},
1185+
},
1186+
Spec: nodedisruptionv1alpha1.NodeDisruptionSpec{
1187+
NodeSelector: metav1.LabelSelector{},
1188+
Type: "maintenance",
1189+
},
1190+
}
1191+
1192+
notFound := errorsk8s.NewNotFound(
1193+
nodedisruptionv1alpha1.GroupVersion.WithResource("nodedisruptions").GroupResource(),
1194+
nd.Name,
1195+
)
1196+
1197+
c := fake.NewClientBuilder().
1198+
WithScheme(s).
1199+
WithObjects(nd).
1200+
WithStatusSubresource(&nodedisruptionv1alpha1.NodeDisruption{}).
1201+
WithInterceptorFuncs(interceptor.Funcs{
1202+
SubResourceUpdate: func(_ context.Context, _ client.Client, subResourceName string, _ client.Object, _ ...client.SubResourceUpdateOption) error {
1203+
if subResourceName == "status" {
1204+
return notFound
1205+
}
1206+
return nil
1207+
},
1208+
}).
1209+
Build()
1210+
1211+
r := &NodeDisruptionReconciler{
1212+
Client: c,
1213+
Scheme: s,
1214+
Config: NodeDisruptionReconcilerConfig{RetryInterval: time.Second},
1215+
}
1216+
1217+
_, err := r.Reconcile(context.Background(), ctrl.Request{
1218+
NamespacedName: types.NamespacedName{Name: nd.Name, Namespace: nd.Namespace},
1219+
})
1220+
assert.NoError(t, err, "NotFound on status update must be swallowed by Reconcile")
1221+
}

0 commit comments

Comments
 (0)