• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

k8snetworkplumbingwg / sriov-network-operator / 26528737080

27 May 2026 05:51PM UTC coverage: 63.396% (-0.02%) from 63.415%
26528737080

push

github

web-flow
Merge pull request #1086 from SchSeba/ocpbugs-64886

Fix NAD creation blocked by stale LASTNETWORKNAMESPACE annotation

3 of 7 new or added lines in 1 file covered. (42.86%)

9 existing lines in 3 files now uncovered.

9465 of 14930 relevant lines covered (63.4%)

0.71 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

79.75
/controllers/generic_network_controller.go
1
/*
2
Copyright 2021.
3

4
Licensed under the Apache License, Version 2.0 (the "License");
5
you may not use this file except in compliance with the License.
6
You may obtain a copy of the License at
7

8
    http://www.apache.org/licenses/LICENSE-2.0
9

10
Unless required by applicable law or agreed to in writing, software
11
distributed under the License is distributed on an "AS IS" BASIS,
12
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
See the License for the specific language governing permissions and
14
limitations under the License.
15
*/
16

17
package controllers
18

19
import (
20
        "context"
21
        "fmt"
22

23
        netattdefv1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1"
24
        corev1 "k8s.io/api/core/v1"
25
        "k8s.io/apimachinery/pkg/api/equality"
26
        "k8s.io/apimachinery/pkg/api/errors"
27
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28
        uns "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
29
        "k8s.io/apimachinery/pkg/runtime"
30
        "k8s.io/apimachinery/pkg/types"
31
        "k8s.io/client-go/util/workqueue"
32
        ctrl "sigs.k8s.io/controller-runtime"
33
        "sigs.k8s.io/controller-runtime/pkg/client"
34
        "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
35
        "sigs.k8s.io/controller-runtime/pkg/event"
36
        "sigs.k8s.io/controller-runtime/pkg/handler"
37
        "sigs.k8s.io/controller-runtime/pkg/log"
38
        "sigs.k8s.io/controller-runtime/pkg/reconcile"
39

40
        sriovnetworkv1 "github.com/k8snetworkplumbingwg/sriov-network-operator/api/v1"
41
        "github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/consts"
42
        "github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/utils"
43
        "github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/vars"
44
)
45

46
type NetworkCRInstance interface {
47
        client.Object
48
        // renders NetAttDef from the network instance
49
        RenderNetAttDef() (*uns.Unstructured, error)
50
        // return name of the target namespace for the network
51
        NetworkNamespace() string
52
}
53

54
// interface which controller should implement to be compatible with genericNetworkReconciler
55
type networkController interface {
56
        reconcile.Reconciler
57
        // GetObject should return CR type which implements networkCRInstance
58
        // interface
59
        GetObject() NetworkCRInstance
60
        // should return CR list type
61
        GetObjectList() client.ObjectList
62
        // should return name of the controller
63
        Name() string
64
}
65

66
func newGenericNetworkReconciler(c client.Client, s *runtime.Scheme, controller networkController) *genericNetworkReconciler {
1✔
67
        return &genericNetworkReconciler{Client: c, Scheme: s, controller: controller}
1✔
68
}
1✔
69

70
// genericNetworkReconciler provide common code for all network controllers
71
type genericNetworkReconciler struct {
72
        client.Client
73
        Scheme     *runtime.Scheme
74
        controller networkController
75
}
76

77
func (r *genericNetworkReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
1✔
78
        reqLogger := log.FromContext(ctx).WithValues(r.controller.Name(), req.NamespacedName)
1✔
79

1✔
80
        reqLogger.Info("Reconciling " + r.controller.Name())
1✔
81
        var err error
1✔
82

1✔
83
        // Fetch instance of the network object
1✔
84
        instance := r.controller.GetObject()
1✔
85
        err = r.Get(ctx, req.NamespacedName, instance)
1✔
86
        if err != nil {
2✔
87
                if errors.IsNotFound(err) {
2✔
88
                        // Request object not found, could have been deleted after reconcile request.
1✔
89
                        // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers.
1✔
90
                        // Return and don't requeue
1✔
91
                        return reconcile.Result{}, nil
1✔
92
                }
1✔
93
                // Error reading the object - requeue the request.
94
                return reconcile.Result{}, err
×
95
        }
96

97
        if instance == nil {
1✔
98
                // Request object not found, could have been deleted after reconcile request.
×
99
                // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers.
×
100
                // Return and don't requeue
×
101
                return reconcile.Result{}, nil
×
102
        }
×
103

104
        if instance.NetworkNamespace() != "" && instance.GetNamespace() != vars.Namespace {
2✔
105
                reqLogger.Error(
1✔
106
                        fmt.Errorf("bad value for NetworkNamespace"),
1✔
107
                        ".spec.networkNamespace can't be specified if the resource belongs to a namespace other than the operator's",
1✔
108
                        "operatorNamespace", vars.Namespace,
1✔
109
                        ".metadata.namespace", instance.GetNamespace(),
1✔
110
                        ".spec.networkNamespace", instance.NetworkNamespace(),
1✔
111
                )
1✔
112
                return reconcile.Result{}, nil
1✔
113
        }
1✔
114

115
        // examine DeletionTimestamp to determine if object is under deletion
116
        if instance.GetDeletionTimestamp().IsZero() {
2✔
117
                // The object is not being deleted, so if it does not have our finalizer,
1✔
118
                // then lets add the finalizer and update the object. This is equivalent
1✔
119
                // registering our finalizer.
1✔
120
                err = r.updateFinalizers(ctx, instance)
1✔
121
                if err != nil {
2✔
122
                        return reconcile.Result{}, err
1✔
123
                }
1✔
124
        } else {
1✔
125
                // The object is being deleted
1✔
126
                err = r.cleanResourcesAndFinalizers(ctx, instance)
1✔
127
                return reconcile.Result{}, err
1✔
128
        }
1✔
129
        raw, err := instance.RenderNetAttDef()
1✔
130
        if err != nil {
1✔
131
                return reconcile.Result{}, err
×
132
        }
×
133
        netAttDef := &netattdefv1.NetworkAttachmentDefinition{}
1✔
134
        err = r.Scheme.Convert(raw, netAttDef, nil)
1✔
135
        if err != nil {
1✔
136
                return reconcile.Result{}, err
×
137
        }
×
138
        // format CNI config json in CR for easier readability
139
        netAttDef.Spec.Config, err = formatJSON(netAttDef.Spec.Config)
1✔
140
        if err != nil {
1✔
141
                reqLogger.Error(err, "Couldn't process rendered NetworkAttachmentDefinition config", "Namespace", netAttDef.Namespace, "Name", netAttDef.Name)
×
142
                return reconcile.Result{}, err
×
143
        }
×
144
        if lnns, ok := instance.GetAnnotations()[sriovnetworkv1.LASTNETWORKNAMESPACE]; ok && netAttDef.GetNamespace() != lnns {
2✔
145
                err = r.Delete(ctx, &netattdefv1.NetworkAttachmentDefinition{
1✔
146
                        ObjectMeta: metav1.ObjectMeta{
1✔
147
                                Name:      instance.GetName(),
1✔
148
                                Namespace: lnns,
1✔
149
                        },
1✔
150
                })
1✔
151
                if err != nil {
2✔
152
                        if !errors.IsNotFound(err) {
1✔
NEW
153
                                reqLogger.Error(err, "Couldn't delete NetworkAttachmentDefinition CR", "Namespace", lnns, "Name", instance.GetName())
×
NEW
154
                                return reconcile.Result{}, err
×
NEW
155
                        }
×
156
                        reqLogger.Info("Old NetworkAttachmentDefinition not found, skipping deletion", "Namespace", lnns, "Name", instance.GetName())
1✔
157
                }
158
                // Clear the stale annotation so we don't retry deletion.
159
                // It will be re-set at creation time (L192).
160
                if err := utils.AnnotateObject(ctx, instance, sriovnetworkv1.LASTNETWORKNAMESPACE, "", r.Client); err != nil {
1✔
NEW
161
                        reqLogger.Error(err, "Couldn't clear LASTNETWORKNAMESPACE annotation", "Name", instance.GetName())
×
162
                        return reconcile.Result{}, err
×
163
                }
×
164
        }
165

166
        if instance.GetNamespace() == netAttDef.Namespace {
2✔
167
                // If the NetAttachDef is in the same namespace of the resource, then we can leverage the OwnerReference field for garbage collector
1✔
168
                if err := controllerutil.SetOwnerReference(instance, netAttDef, r.Scheme); err != nil {
1✔
169
                        return reconcile.Result{}, err
×
170
                }
×
171
        }
172

173
        // Check if this NetworkAttachmentDefinition already exists
174
        found := &netattdefv1.NetworkAttachmentDefinition{}
1✔
175
        err = r.Get(ctx, types.NamespacedName{Name: netAttDef.Name, Namespace: netAttDef.Namespace}, found)
1✔
176
        if err != nil {
2✔
177
                if errors.IsNotFound(err) {
2✔
178
                        targetNamespace := &corev1.Namespace{}
1✔
179
                        err = r.Get(ctx, types.NamespacedName{Name: netAttDef.Namespace}, targetNamespace)
1✔
180
                        if errors.IsNotFound(err) {
2✔
181
                                reqLogger.Info("Target namespace doesn't exist, NetworkAttachmentDefinition will be created when namespace is available", "Namespace", netAttDef.Namespace, "Name", netAttDef.Name)
1✔
182
                                return reconcile.Result{}, nil
1✔
183
                        }
1✔
184

185
                        reqLogger.Info("NetworkAttachmentDefinition CR not exist, creating")
1✔
186
                        err = r.Create(ctx, netAttDef)
1✔
187
                        if err != nil {
1✔
188
                                reqLogger.Error(err, "Couldn't create NetworkAttachmentDefinition CR", "Namespace", netAttDef.Namespace, "Name", netAttDef.Name)
×
189
                                return reconcile.Result{}, err
×
190
                        }
×
191

192
                        err = utils.AnnotateObject(ctx, instance, sriovnetworkv1.LASTNETWORKNAMESPACE, netAttDef.Namespace, r.Client)
1✔
193
                        if err != nil {
1✔
194
                                return reconcile.Result{}, err
×
195
                        }
×
196
                } else {
×
197
                        reqLogger.Error(err, "Couldn't get NetworkAttachmentDefinition CR", "Namespace", netAttDef.Namespace, "Name", netAttDef.Name)
×
198
                        return reconcile.Result{}, err
×
199
                }
×
200
        } else {
1✔
201
                reqLogger.Info("NetworkAttachmentDefinition CR already exist")
1✔
202

1✔
203
                foundOwner := found.GetAnnotations()[consts.OwnerRefAnnotation]
1✔
204
                expectedOwner := netAttDef.GetAnnotations()[consts.OwnerRefAnnotation]
1✔
205

1✔
206
                // Note for the future: the `foundOwner != ""` condition can be removed to make the operator not touching the NetworkAttachmentDefinition created
1✔
207
                // by the user.
1✔
208
                if foundOwner != "" && foundOwner != expectedOwner {
2✔
209
                        reqLogger.Info("A NetworkAttachmentDefinition with the same name already exists and it does not belong to this resource",
1✔
210
                                "Namespace", netAttDef.Namespace, "Name", netAttDef.Name,
1✔
211
                                "CurrentOwner", foundOwner, "ExpectedOwner", expectedOwner,
1✔
212
                        )
1✔
213
                        return reconcile.Result{}, nil
1✔
214
                }
1✔
215

216
                if !equality.Semantic.DeepEqual(found.Spec, netAttDef.Spec) || !equality.Semantic.DeepEqual(found.GetAnnotations(), netAttDef.GetAnnotations()) {
2✔
217
                        reqLogger.Info("Update NetworkAttachmentDefinition CR", "Namespace", netAttDef.Namespace, "Name", netAttDef.Name)
1✔
218
                        netAttDef.SetResourceVersion(found.GetResourceVersion())
1✔
219

1✔
220
                        err = r.Update(ctx, netAttDef)
1✔
221
                        if err != nil {
1✔
222
                                reqLogger.Error(err, "Couldn't update NetworkAttachmentDefinition CR", "Namespace", netAttDef.Namespace, "Name", netAttDef.Name)
×
223
                                return reconcile.Result{}, err
×
224
                        }
×
225
                }
226
        }
227

228
        return ctrl.Result{}, nil
1✔
229
}
230

231
// SetupWithManager sets up the controller with the Manager.
232
func (r *genericNetworkReconciler) SetupWithManager(mgr ctrl.Manager) error {
1✔
233
        // Reconcile when the target namespace is created after the network object.
1✔
234
        namespaceHandler := handler.Funcs{
1✔
235
                CreateFunc: r.namespaceHandlerCreate,
1✔
236
        }
1✔
237
        return ctrl.NewControllerManagedBy(mgr).
1✔
238
                For(r.controller.GetObject()).
1✔
239
                Watches(&netattdefv1.NetworkAttachmentDefinition{}, handler.EnqueueRequestsFromMapFunc(r.handleNetAttDef)).
1✔
240
                Watches(&corev1.Namespace{}, &namespaceHandler).
1✔
241
                Complete(r.controller)
1✔
242
}
1✔
243

244
func (r *genericNetworkReconciler) handleNetAttDef(ctx context.Context, obj client.Object) []reconcile.Request {
1✔
245
        ret := []reconcile.Request{}
1✔
246
        instance := r.controller.GetObject()
1✔
247
        nadNamespacedName := types.NamespacedName{Namespace: obj.GetNamespace(), Name: obj.GetName()}
1✔
248

1✔
249
        err := r.Get(ctx, nadNamespacedName, instance)
1✔
250
        if err == nil {
2✔
251
                // Found a NetworkObject in the same namespace as the NetworkAttachmentDefinition, reconcile it
1✔
252
                ret = append(ret, reconcile.Request{NamespacedName: nadNamespacedName})
1✔
253
        } else if !errors.IsNotFound(err) {
2✔
254
                log.Log.WithName(r.controller.Name()+" handleNetAttDef").Error(err, "can't get object", "object", nadNamespacedName)
×
255
        }
×
256

257
        // Not found, try to find the NetworkObject in the operator's namespace
258
        operatorNamespacedName := types.NamespacedName{Namespace: vars.Namespace, Name: obj.GetName()}
1✔
259
        err = r.Get(ctx, operatorNamespacedName, instance)
1✔
260
        if err == nil {
2✔
261
                // Found a NetworkObject in the operator's namespace, reconcile it
1✔
262
                ret = append(ret, reconcile.Request{NamespacedName: operatorNamespacedName})
1✔
263
        } else if !errors.IsNotFound(err) {
2✔
264
                log.Log.WithName(r.controller.Name()+" handleNetAttDef").Error(err, "can't get object", "object", operatorNamespacedName)
×
265
        }
×
266

267
        return ret
1✔
268
}
269

270
func (r *genericNetworkReconciler) namespaceHandlerCreate(ctx context.Context, e event.TypedCreateEvent[client.Object], w workqueue.TypedRateLimitingInterface[reconcile.Request]) {
1✔
271
        networkList := r.controller.GetObjectList()
1✔
272
        err := r.List(ctx,
1✔
273
                networkList,
1✔
274
                client.MatchingFields{"spec.networkNamespace": e.Object.GetName()},
1✔
275
        )
1✔
276
        logger := log.Log.WithName(r.controller.Name() + " reconciler")
1✔
277
        if err != nil {
1✔
278
                logger.Info("Can't list networks for namespace", "resource", e.Object.GetName(), "error", err)
×
279
                return
×
280
        }
×
281
        unsContent, err := runtime.DefaultUnstructuredConverter.ToUnstructured(networkList)
1✔
282
        if err != nil {
1✔
283
                logger.Info("Can't convert network list to unstructured object", "resource", e.Object.GetName(), "error", err)
×
284
                return
×
285
        }
×
286
        unsList := &uns.Unstructured{}
1✔
287
        unsList.SetUnstructuredContent(unsContent)
1✔
288
        _ = unsList.EachListItem(func(o runtime.Object) error {
2✔
289
                unsObj := o.(*uns.Unstructured)
1✔
290
                w.Add(reconcile.Request{NamespacedName: types.NamespacedName{
1✔
291
                        Namespace: unsObj.GetNamespace(),
1✔
292
                        Name:      unsObj.GetName(),
1✔
293
                }})
1✔
294
                return nil
1✔
295
        })
1✔
296
}
297

298
// deleteNetAttDef deletes the generated net-att-def CR
299
func (r *genericNetworkReconciler) deleteNetAttDef(ctx context.Context, cr NetworkCRInstance) error {
1✔
300
        // Fetch the NetworkAttachmentDefinition instance
1✔
301
        namespace := cr.NetworkNamespace()
1✔
302
        if namespace == "" {
2✔
303
                namespace = cr.GetNamespace()
1✔
304
        }
1✔
305
        instance := &netattdefv1.NetworkAttachmentDefinition{ObjectMeta: metav1.ObjectMeta{Name: cr.GetName(), Namespace: namespace}}
1✔
306
        err := r.Delete(ctx, instance)
1✔
307
        if err != nil {
2✔
308
                if errors.IsNotFound(err) {
2✔
309
                        return nil
1✔
310
                }
1✔
311
                return err
×
312
        }
313
        return nil
1✔
314
}
315

316
func (r *genericNetworkReconciler) updateFinalizers(ctx context.Context, instance NetworkCRInstance) error {
1✔
317
        if instance.GetNamespace() != vars.Namespace {
2✔
318
                // If the resource is in a namespace different than the operator one, then the NetworkAttachmentDefinition will
1✔
319
                // be created in the same namespace and its deletion can be handled by OwnerReferences. There is no need for finalizers
1✔
320
                return nil
1✔
321
        }
1✔
322

323
        instanceFinalizers := instance.GetFinalizers()
1✔
324
        if !sriovnetworkv1.StringInArray(sriovnetworkv1.NETATTDEFFINALIZERNAME, instanceFinalizers) {
2✔
325
                instance.SetFinalizers(append(instanceFinalizers, sriovnetworkv1.NETATTDEFFINALIZERNAME))
1✔
326
                if err := r.Update(ctx, instance); err != nil {
2✔
327
                        return err
1✔
328
                }
1✔
329
        }
330

331
        return nil
1✔
332
}
333

334
func (r *genericNetworkReconciler) cleanResourcesAndFinalizers(ctx context.Context, instance NetworkCRInstance) error {
1✔
335
        instanceFinalizers := instance.GetFinalizers()
1✔
336

1✔
337
        if sriovnetworkv1.StringInArray(sriovnetworkv1.NETATTDEFFINALIZERNAME, instanceFinalizers) {
2✔
338
                // our finalizer is present, so lets handle any external dependency
1✔
339
                log.FromContext(ctx).Info("delete NetworkAttachmentDefinition CR", "Namespace", instance.NetworkNamespace(), "Name", instance.GetName())
1✔
340
                if err := r.deleteNetAttDef(ctx, instance); err != nil {
1✔
341
                        // if fail to delete the external dependency here, return with error
×
342
                        // so that it can be retried
×
343
                        return err
×
344
                }
×
345
                // remove our finalizer from the list and update it.
346
                newFinalizers, found := sriovnetworkv1.RemoveString(sriovnetworkv1.NETATTDEFFINALIZERNAME, instanceFinalizers)
1✔
347
                if found {
2✔
348
                        instance.SetFinalizers(newFinalizers)
1✔
349
                        if err := r.Update(ctx, instance); err != nil {
2✔
350
                                return err
1✔
351
                        }
1✔
352
                }
353
        }
354
        return nil
1✔
355
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc