Skip to content

Commit dbcd292

Browse files
add finaliser to GPUCluster CR using PATCH
Signed-off-by: Tariq Ibrahim <tibrahim@nvidia.com> Co-authored-by: Rajath Agasthya <ragasthya@nvidia.com>
1 parent 8e5dc24 commit dbcd292

3 files changed

Lines changed: 74 additions & 5 deletions

File tree

‎controllers/gpucluster_controller.go‎

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import (
4545
"github.com/NVIDIA/gpu-operator/internal/conditions"
4646
"github.com/NVIDIA/gpu-operator/internal/consts"
4747
"github.com/NVIDIA/gpu-operator/internal/state"
48+
"github.com/NVIDIA/gpu-operator/internal/utils"
4849
)
4950

5051
// gpuClusterFinalizer holds the GPUCluster until reconcileDelete has ordered teardown.
@@ -97,11 +98,9 @@ func (r *GPUClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request)
9798
if !instance.DeletionTimestamp.IsZero() {
9899
return r.reconcileDelete(ctx, instance)
99100
}
100-
if !controllerutil.ContainsFinalizer(instance, gpuClusterFinalizer) {
101-
controllerutil.AddFinalizer(instance, gpuClusterFinalizer)
102-
if err := r.Update(ctx, instance); err != nil {
103-
return ctrl.Result{}, fmt.Errorf("error adding finalizer: %w", err)
104-
}
101+
102+
if err := utils.EnsureFinalizer(ctx, r.Client, instance, gpuClusterFinalizer); err != nil {
103+
return ctrl.Result{}, fmt.Errorf("error adding finalizer to GPUCluster %s: %w", req.NamespacedName, err)
105104
}
106105

107106
// GPUCluster (DRA stack) may coexist with a ClusterPolicy (device-plugin

‎internal/utils/utils.go‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package utils
1818

1919
import (
20+
"context"
2021
"fmt"
2122
"hash"
2223
"hash/fnv"
@@ -29,6 +30,8 @@ import (
2930

3031
"github.com/davecgh/go-spew/spew"
3132
"k8s.io/apimachinery/pkg/util/rand"
33+
"sigs.k8s.io/controller-runtime/pkg/client"
34+
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
3235
)
3336

3437
// GetFilesWithSuffix returns all files under a given base directory that have a specific suffix
@@ -163,3 +166,15 @@ func WriteFileAtomically(path, content string) error {
163166
}
164167
return nil
165168
}
169+
170+
// EnsureFinalizer adds a finalizer to an object that has not been marked for deletion.
171+
// It is idempotent and returns an error only if an attempt to add the finalizer has failed.
172+
func EnsureFinalizer(ctx context.Context, c client.Client, o client.Object, finalizer string) error {
173+
if !o.GetDeletionTimestamp().IsZero() || controllerutil.ContainsFinalizer(o, finalizer) {
174+
return nil
175+
}
176+
177+
original := o.DeepCopyObject().(client.Object)
178+
controllerutil.AddFinalizer(o, finalizer)
179+
return c.Patch(ctx, o, client.MergeFromWithOptions(original, client.MergeFromWithOptimisticLock{}))
180+
}

‎internal/utils/utils_test.go‎

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,17 @@
1717
package utils
1818

1919
import (
20+
"context"
2021
"reflect"
2122
"testing"
2223

2324
"github.com/stretchr/testify/assert"
25+
corev1 "k8s.io/api/core/v1"
26+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27+
"sigs.k8s.io/controller-runtime/pkg/client/fake"
28+
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
29+
30+
"k8s.io/client-go/kubernetes/scheme"
2431
)
2532

2633
func TestGetObjectHash(t *testing.T) {
@@ -190,3 +197,51 @@ func TestGetStringHash(t *testing.T) {
190197
assert.Equal(t, tc.expected, actual)
191198
}
192199
}
200+
201+
func TestEnsureFinalizer(t *testing.T) {
202+
const testFinalizer = "test.io/finalizer"
203+
204+
s := scheme.Scheme
205+
206+
newObj := func(name string, finalizers ...string) *corev1.ConfigMap {
207+
return &corev1.ConfigMap{
208+
ObjectMeta: metav1.ObjectMeta{
209+
Name: name,
210+
Namespace: "default",
211+
ResourceVersion: "1",
212+
Finalizers: finalizers,
213+
},
214+
}
215+
}
216+
217+
t.Run("adds finalizer when not present", func(t *testing.T) {
218+
obj := newObj("obj")
219+
c := fake.NewClientBuilder().WithScheme(s).WithObjects(obj).Build()
220+
221+
err := EnsureFinalizer(context.Background(), c, obj, testFinalizer)
222+
assert.NoError(t, err)
223+
assert.True(t, controllerutil.ContainsFinalizer(obj, testFinalizer))
224+
})
225+
226+
t.Run("no-op when finalizer already present", func(t *testing.T) {
227+
obj := newObj("obj", testFinalizer)
228+
// object not registered in client — Patch would fail if reached
229+
c := fake.NewClientBuilder().WithScheme(s).Build()
230+
231+
err := EnsureFinalizer(context.Background(), c, obj, testFinalizer)
232+
assert.NoError(t, err)
233+
assert.True(t, controllerutil.ContainsFinalizer(obj, testFinalizer))
234+
})
235+
236+
t.Run("no-op when object is being deleted", func(t *testing.T) {
237+
now := metav1.Now()
238+
obj := newObj("obj")
239+
obj.DeletionTimestamp = &now
240+
// object not registered in client — Patch would fail if reached
241+
c := fake.NewClientBuilder().WithScheme(s).Build()
242+
243+
err := EnsureFinalizer(context.Background(), c, obj, testFinalizer)
244+
assert.NoError(t, err)
245+
assert.False(t, controllerutil.ContainsFinalizer(obj, testFinalizer))
246+
})
247+
}

0 commit comments

Comments
 (0)