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

k8snetworkplumbingwg / sriov-network-operator / 19137964106

06 Nov 2025 01:54PM UTC coverage: 62.394% (+0.04%) from 62.358%
19137964106

Pull #962

github

web-flow
Merge 90b11c821 into 1821d9dc1
Pull Request #962: e2e: Refactor `PF shutdown` test case

8757 of 14035 relevant lines covered (62.39%)

0.7 hits per line

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

77.49
/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 {
1✔
145
                err = r.Delete(ctx, &netattdefv1.NetworkAttachmentDefinition{
×
146
                        ObjectMeta: metav1.ObjectMeta{
×
147
                                Name:      instance.GetName(),
×
148
                                Namespace: lnns,
×
149
                        },
×
150
                })
×
151
                if err != nil {
×
152
                        reqLogger.Error(err, "Couldn't delete NetworkAttachmentDefinition CR", "Namespace", instance.GetName(), "Name", lnns)
×
153
                        return reconcile.Result{}, err
×
154
                }
×
155
        }
156

157
        if instance.GetNamespace() == netAttDef.Namespace {
2✔
158
                // If the NetAttachDef is in the same namespace of the resource, then we can leverage the OwnerReference field for garbage collector
1✔
159
                if err := controllerutil.SetOwnerReference(instance, netAttDef, r.Scheme); err != nil {
1✔
160
                        return reconcile.Result{}, err
×
161
                }
×
162
        }
163

164
        // Check if this NetworkAttachmentDefinition already exists
165
        found := &netattdefv1.NetworkAttachmentDefinition{}
1✔
166
        err = r.Get(ctx, types.NamespacedName{Name: netAttDef.Name, Namespace: netAttDef.Namespace}, found)
1✔
167
        if err != nil {
2✔
168
                if errors.IsNotFound(err) {
2✔
169
                        targetNamespace := &corev1.Namespace{}
1✔
170
                        err = r.Get(ctx, types.NamespacedName{Name: netAttDef.Namespace}, targetNamespace)
1✔
171
                        if errors.IsNotFound(err) {
2✔
172
                                reqLogger.Info("Target namespace doesn't exist, NetworkAttachmentDefinition will be created when namespace is available", "Namespace", netAttDef.Namespace, "Name", netAttDef.Name)
1✔
173
                                return reconcile.Result{}, nil
1✔
174
                        }
1✔
175

176
                        reqLogger.Info("NetworkAttachmentDefinition CR not exist, creating")
1✔
177
                        err = r.Create(ctx, netAttDef)
1✔
178
                        if err != nil {
1✔
179
                                reqLogger.Error(err, "Couldn't create NetworkAttachmentDefinition CR", "Namespace", netAttDef.Namespace, "Name", netAttDef.Name)
×
180
                                return reconcile.Result{}, err
×
181
                        }
×
182

183
                        err = utils.AnnotateObject(ctx, instance, sriovnetworkv1.LASTNETWORKNAMESPACE, netAttDef.Namespace, r.Client)
1✔
184
                        if err != nil {
1✔
185
                                return reconcile.Result{}, err
×
186
                        }
×
187
                } else {
×
188
                        reqLogger.Error(err, "Couldn't get NetworkAttachmentDefinition CR", "Namespace", netAttDef.Namespace, "Name", netAttDef.Name)
×
189
                        return reconcile.Result{}, err
×
190
                }
×
191
        } else {
1✔
192
                reqLogger.Info("NetworkAttachmentDefinition CR already exist")
1✔
193

1✔
194
                foundOwner := found.GetAnnotations()[consts.OwnerRefAnnotation]
1✔
195
                expectedOwner := netAttDef.GetAnnotations()[consts.OwnerRefAnnotation]
1✔
196

1✔
197
                // Note for the future: the `foundOwner != ""` condition can be removed to make the operator not touching the NetworkAttachmentDefinition created
1✔
198
                // by the user.
1✔
199
                if foundOwner != "" && foundOwner != expectedOwner {
2✔
200
                        reqLogger.Info("A NetworkAttachmentDefinition with the same name already exists and it does not belong to this resource",
1✔
201
                                "Namespace", netAttDef.Namespace, "Name", netAttDef.Name,
1✔
202
                                "CurrentOwner", foundOwner, "ExpectedOwner", expectedOwner,
1✔
203
                        )
1✔
204
                        return reconcile.Result{}, nil
1✔
205
                }
1✔
206

207
                if !equality.Semantic.DeepEqual(found.Spec, netAttDef.Spec) || !equality.Semantic.DeepEqual(found.GetAnnotations(), netAttDef.GetAnnotations()) {
2✔
208
                        reqLogger.Info("Update NetworkAttachmentDefinition CR", "Namespace", netAttDef.Namespace, "Name", netAttDef.Name)
1✔
209
                        netAttDef.SetResourceVersion(found.GetResourceVersion())
1✔
210

1✔
211
                        err = r.Update(ctx, netAttDef)
1✔
212
                        if err != nil {
1✔
213
                                reqLogger.Error(err, "Couldn't update NetworkAttachmentDefinition CR", "Namespace", netAttDef.Namespace, "Name", netAttDef.Name)
×
214
                                return reconcile.Result{}, err
×
215
                        }
×
216
                }
217
        }
218

219
        return ctrl.Result{}, nil
1✔
220
}
221

222
// SetupWithManager sets up the controller with the Manager.
223
func (r *genericNetworkReconciler) SetupWithManager(mgr ctrl.Manager) error {
1✔
224
        // Reconcile when the target namespace is created after the network object.
1✔
225
        namespaceHandler := handler.Funcs{
1✔
226
                CreateFunc: r.namespaceHandlerCreate,
1✔
227
        }
1✔
228
        return ctrl.NewControllerManagedBy(mgr).
1✔
229
                For(r.controller.GetObject()).
1✔
230
                Watches(&netattdefv1.NetworkAttachmentDefinition{}, handler.EnqueueRequestsFromMapFunc(r.handleNetAttDef)).
1✔
231
                Watches(&corev1.Namespace{}, &namespaceHandler).
1✔
232
                Complete(r.controller)
1✔
233
}
1✔
234

235
func (r *genericNetworkReconciler) handleNetAttDef(ctx context.Context, obj client.Object) []reconcile.Request {
1✔
236
        ret := []reconcile.Request{}
1✔
237
        instance := r.controller.GetObject()
1✔
238
        nadNamespacedName := types.NamespacedName{Namespace: obj.GetNamespace(), Name: obj.GetName()}
1✔
239

1✔
240
        err := r.Get(ctx, nadNamespacedName, instance)
1✔
241
        if err == nil {
2✔
242
                // Found a NetworkObject in the same namespace as the NetworkAttachmentDefinition, reconcile it
1✔
243
                ret = append(ret, reconcile.Request{NamespacedName: nadNamespacedName})
1✔
244
        } else if !errors.IsNotFound(err) {
2✔
245
                log.Log.WithName(r.controller.Name()+" handleNetAttDef").Error(err, "can't get object", "object", nadNamespacedName)
×
246
        }
×
247

248
        // Not found, try to find the NetworkObject in the operator's namespace
249
        operatorNamespacedName := types.NamespacedName{Namespace: vars.Namespace, Name: obj.GetName()}
1✔
250
        err = r.Get(ctx, operatorNamespacedName, instance)
1✔
251
        if err == nil {
2✔
252
                // Found a NetworkObject in the operator's namespace, reconcile it
1✔
253
                ret = append(ret, reconcile.Request{NamespacedName: operatorNamespacedName})
1✔
254
        } else if !errors.IsNotFound(err) {
2✔
255
                log.Log.WithName(r.controller.Name()+" handleNetAttDef").Error(err, "can't get object", "object", operatorNamespacedName)
×
256
        }
×
257

258
        return ret
1✔
259
}
260

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

289
// deleteNetAttDef deletes the generated net-att-def CR
290
func (r *genericNetworkReconciler) deleteNetAttDef(ctx context.Context, cr NetworkCRInstance) error {
1✔
291
        // Fetch the NetworkAttachmentDefinition instance
1✔
292
        namespace := cr.NetworkNamespace()
1✔
293
        if namespace == "" {
2✔
294
                namespace = cr.GetNamespace()
1✔
295
        }
1✔
296
        instance := &netattdefv1.NetworkAttachmentDefinition{ObjectMeta: metav1.ObjectMeta{Name: cr.GetName(), Namespace: namespace}}
1✔
297
        err := r.Delete(ctx, instance)
1✔
298
        if err != nil {
2✔
299
                if errors.IsNotFound(err) {
2✔
300
                        return nil
1✔
301
                }
1✔
302
                return err
×
303
        }
304
        return nil
1✔
305
}
306

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

314
        instanceFinalizers := instance.GetFinalizers()
1✔
315
        if !sriovnetworkv1.StringInArray(sriovnetworkv1.NETATTDEFFINALIZERNAME, instanceFinalizers) {
2✔
316
                instance.SetFinalizers(append(instanceFinalizers, sriovnetworkv1.NETATTDEFFINALIZERNAME))
1✔
317
                if err := r.Update(ctx, instance); err != nil {
2✔
318
                        return err
1✔
319
                }
1✔
320
        }
321

322
        return nil
1✔
323
}
324

325
func (r *genericNetworkReconciler) cleanResourcesAndFinalizers(ctx context.Context, instance NetworkCRInstance) error {
1✔
326
        instanceFinalizers := instance.GetFinalizers()
1✔
327

1✔
328
        if sriovnetworkv1.StringInArray(sriovnetworkv1.NETATTDEFFINALIZERNAME, instanceFinalizers) {
2✔
329
                // our finalizer is present, so lets handle any external dependency
1✔
330
                log.FromContext(ctx).Info("delete NetworkAttachmentDefinition CR", "Namespace", instance.NetworkNamespace(), "Name", instance.GetName())
1✔
331
                if err := r.deleteNetAttDef(ctx, instance); err != nil {
1✔
332
                        // if fail to delete the external dependency here, return with error
×
333
                        // so that it can be retried
×
334
                        return err
×
335
                }
×
336
                // remove our finalizer from the list and update it.
337
                newFinalizers, found := sriovnetworkv1.RemoveString(sriovnetworkv1.NETATTDEFFINALIZERNAME, instanceFinalizers)
1✔
338
                if found {
2✔
339
                        instance.SetFinalizers(newFinalizers)
1✔
340
                        if err := r.Update(ctx, instance); err != nil {
2✔
341
                                return err
1✔
342
                        }
1✔
343
                }
344
        }
345
        return nil
1✔
346
}
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

© 2025 Coveralls, Inc