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

k8snetworkplumbingwg / sriov-network-operator / 14725181549

29 Apr 2025 06:54AM UTC coverage: 61.624% (+0.01%) from 61.612%
14725181549

Pull #847

github

web-flow
Merge 9d4fd607c into b416aa462
Pull Request #847: Make kargs.sh work with systemd service and for Ubuntu

3 of 5 new or added lines in 1 file covered. (60.0%)

2 existing lines in 1 file now uncovered.

8525 of 13834 relevant lines covered (61.62%)

0.69 hits per line

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

70.61
/controllers/helper.go
1
/*
2

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
        "bytes"
21
        "context"
22
        "encoding/json"
23
        "fmt"
24
        "os"
25
        "strings"
26

27
        errs "github.com/pkg/errors"
28
        appsv1 "k8s.io/api/apps/v1"
29
        corev1 "k8s.io/api/core/v1"
30
        "k8s.io/apimachinery/pkg/api/equality"
31
        "k8s.io/apimachinery/pkg/api/errors"
32
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
33
        uns "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
34
        "k8s.io/apimachinery/pkg/labels"
35
        "k8s.io/apimachinery/pkg/runtime"
36
        "k8s.io/apimachinery/pkg/types"
37
        "k8s.io/apimachinery/pkg/util/intstr"
38
        kscheme "k8s.io/client-go/kubernetes/scheme"
39
        k8sclient "sigs.k8s.io/controller-runtime/pkg/client"
40
        "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
41
        "sigs.k8s.io/controller-runtime/pkg/event"
42
        "sigs.k8s.io/controller-runtime/pkg/log"
43
        "sigs.k8s.io/controller-runtime/pkg/predicate"
44

45
        sriovnetworkv1 "github.com/k8snetworkplumbingwg/sriov-network-operator/api/v1"
46
        "github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/apply"
47
        constants "github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/consts"
48
        "github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/render"
49
        "github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/vars"
50
)
51

52
var (
53
        webhooks = map[string]string{
54
                constants.InjectorWebHookName: constants.InjectorWebHookPath,
55
                constants.OperatorWebHookName: constants.OperatorWebHookPath,
56
        }
57
        oneNode           = intstr.FromInt32(1)
58
        defaultPoolConfig = &sriovnetworkv1.SriovNetworkPoolConfig{Spec: sriovnetworkv1.SriovNetworkPoolConfigSpec{
59
                MaxUnavailable: &oneNode,
60
                NodeSelector:   &metav1.LabelSelector{},
61
                RdmaMode:       ""}}
62
)
63

64
const (
65
        clusterRoleResourceName               = "ClusterRole"
66
        clusterRoleBindingResourceName        = "ClusterRoleBinding"
67
        mutatingWebhookConfigurationCRDName   = "MutatingWebhookConfiguration"
68
        validatingWebhookConfigurationCRDName = "ValidatingWebhookConfiguration"
69
        machineConfigCRDName                  = "MachineConfig"
70
        trueString                            = "true"
71
)
72

73
type DrainAnnotationPredicate struct {
74
        predicate.Funcs
75
}
76

77
func (DrainAnnotationPredicate) Create(e event.CreateEvent) bool {
1✔
78
        if e.Object == nil {
1✔
79
                return false
×
80
        }
×
81

82
        if _, hasAnno := e.Object.GetAnnotations()[constants.NodeDrainAnnotation]; hasAnno {
2✔
83
                return true
1✔
84
        }
1✔
85
        return false
×
86
}
87

88
func (DrainAnnotationPredicate) Update(e event.UpdateEvent) bool {
1✔
89
        if e.ObjectOld == nil {
1✔
90
                return false
×
91
        }
×
92
        if e.ObjectNew == nil {
1✔
93
                return false
×
94
        }
×
95

96
        oldAnno, hasOldAnno := e.ObjectOld.GetAnnotations()[constants.NodeDrainAnnotation]
1✔
97
        newAnno, hasNewAnno := e.ObjectNew.GetAnnotations()[constants.NodeDrainAnnotation]
1✔
98

1✔
99
        if !hasOldAnno && hasNewAnno {
1✔
100
                return true
×
101
        }
×
102

103
        return oldAnno != newAnno
1✔
104
}
105

106
type DrainStateAnnotationPredicate struct {
107
        predicate.Funcs
108
}
109

110
func (DrainStateAnnotationPredicate) Create(e event.CreateEvent) bool {
1✔
111
        return e.Object != nil
1✔
112
}
1✔
113

114
func (DrainStateAnnotationPredicate) Update(e event.UpdateEvent) bool {
1✔
115
        if e.ObjectOld == nil {
1✔
116
                return false
×
117
        }
×
118
        if e.ObjectNew == nil {
1✔
119
                return false
×
120
        }
×
121

122
        oldAnno, hasOldAnno := e.ObjectOld.GetLabels()[constants.NodeStateDrainAnnotationCurrent]
1✔
123
        newAnno, hasNewAnno := e.ObjectNew.GetLabels()[constants.NodeStateDrainAnnotationCurrent]
1✔
124

1✔
125
        if !hasOldAnno || !hasNewAnno {
2✔
126
                return true
1✔
127
        }
1✔
128

129
        return oldAnno != newAnno
1✔
130
}
131

132
func GetImagePullSecrets() []string {
1✔
133
        imagePullSecrets := os.Getenv("IMAGE_PULL_SECRETS")
1✔
134
        if imagePullSecrets != "" {
1✔
135
                return strings.Split(imagePullSecrets, ",")
×
136
        } else {
1✔
137
                return []string{}
1✔
138
        }
1✔
139
}
140

141
func formatJSON(str string) (string, error) {
1✔
142
        var prettyJSON bytes.Buffer
1✔
143
        if err := json.Indent(&prettyJSON, []byte(str), "", "    "); err != nil {
1✔
144
                return "", err
×
145
        }
×
146
        return prettyJSON.String(), nil
1✔
147
}
148

149
// GetDefaultNodeSelector return a nodeSelector with worker and linux os
150
func GetDefaultNodeSelector() map[string]string {
1✔
151
        return map[string]string{
1✔
152
                "node-role.kubernetes.io/worker": "",
1✔
153
                "kubernetes.io/os":               "linux",
1✔
154
        }
1✔
155
}
1✔
156

157
// GetDefaultNodeSelectorForDevicePlugin return a nodeSelector with worker linux os
158
// and the enabled sriov device plugin
159
func GetNodeSelectorForDevicePlugin(dc *sriovnetworkv1.SriovOperatorConfig) map[string]string {
1✔
160
        if len(dc.Spec.ConfigDaemonNodeSelector) == 0 {
2✔
161
                return map[string]string{
1✔
162
                        "kubernetes.io/os":               "linux",
1✔
163
                        constants.SriovDevicePluginLabel: constants.SriovDevicePluginLabelEnabled,
1✔
164
                }
1✔
165
        }
1✔
166

167
        tmp := dc.Spec.DeepCopy()
1✔
168
        tmp.ConfigDaemonNodeSelector[constants.SriovDevicePluginLabel] = constants.SriovDevicePluginLabelEnabled
1✔
169
        return tmp.ConfigDaemonNodeSelector
1✔
170
}
171

172
func syncPluginDaemonObjs(ctx context.Context,
173
        client k8sclient.Client,
174
        scheme *runtime.Scheme,
175
        dc *sriovnetworkv1.SriovOperatorConfig) error {
1✔
176
        logger := log.Log.WithName("syncPluginDaemonObjs")
1✔
177
        logger.V(1).Info("Start to sync sriov daemons objects")
1✔
178

1✔
179
        // render plugin manifests
1✔
180
        data := render.MakeRenderData()
1✔
181
        data.Data["Namespace"] = vars.Namespace
1✔
182
        data.Data["SRIOVDevicePluginImage"] = os.Getenv("SRIOV_DEVICE_PLUGIN_IMAGE")
1✔
183
        data.Data["ReleaseVersion"] = os.Getenv("RELEASEVERSION")
1✔
184
        data.Data["ResourcePrefix"] = vars.ResourcePrefix
1✔
185
        data.Data["ImagePullSecrets"] = GetImagePullSecrets()
1✔
186
        data.Data["NodeSelectorField"] = GetNodeSelectorForDevicePlugin(dc)
1✔
187
        data.Data["UseCDI"] = dc.Spec.UseCDI
1✔
188
        objs, err := renderDsForCR(constants.PluginPath, &data)
1✔
189
        if err != nil {
1✔
190
                logger.Error(err, "Fail to render SR-IoV manifests")
×
191
                return err
×
192
        }
×
193

194
        // Sync DaemonSets
195
        for _, obj := range objs {
2✔
196
                err = syncDsObject(ctx, client, scheme, dc, obj)
1✔
197
                if err != nil {
1✔
198
                        logger.Error(err, "Couldn't sync SR-IoV daemons objects")
×
199
                        return err
×
200
                }
×
201
        }
202

203
        return nil
1✔
204
}
205

206
func syncDsObject(ctx context.Context, client k8sclient.Client, scheme *runtime.Scheme, dc *sriovnetworkv1.SriovOperatorConfig, obj *uns.Unstructured) error {
1✔
207
        logger := log.Log.WithName("syncDsObject")
1✔
208
        kind := obj.GetKind()
1✔
209
        logger.V(1).Info("Start to sync Objects", "Kind", kind)
1✔
210
        switch kind {
1✔
211
        case constants.ServiceAccount, constants.Role, constants.RoleBinding:
1✔
212
                if err := controllerutil.SetControllerReference(dc, obj, scheme); err != nil {
1✔
213
                        return err
×
214
                }
×
215
                if err := apply.ApplyObject(ctx, client, obj); err != nil {
1✔
216
                        logger.Error(err, "Fail to sync", "Kind", kind)
×
217
                        return err
×
218
                }
×
219
        case constants.DaemonSet:
1✔
220
                ds := &appsv1.DaemonSet{}
1✔
221
                err := scheme.Convert(obj, ds, nil)
1✔
222
                if err != nil {
1✔
223
                        logger.Error(err, "Fail to convert to DaemonSet")
×
224
                        return err
×
225
                }
×
226
                err = syncDaemonSet(ctx, client, scheme, dc, ds)
1✔
227
                if err != nil {
1✔
228
                        logger.Error(err, "Fail to sync DaemonSet", "Namespace", ds.Namespace, "Name", ds.Name)
×
229
                        return err
×
230
                }
×
231
        }
232
        return nil
1✔
233
}
234

235
// renderDsForCR returns a busybox pod with the same name/namespace as the cr
236
func renderDsForCR(path string, data *render.RenderData) ([]*uns.Unstructured, error) {
1✔
237
        logger := log.Log.WithName("renderDsForCR")
1✔
238
        logger.V(1).Info("Start to render objects")
1✔
239

1✔
240
        objs, err := render.RenderDir(path, data)
1✔
241
        if err != nil {
1✔
242
                return nil, errs.Wrap(err, "failed to render SR-IOV Network Operator manifests")
×
243
        }
×
244
        return objs, nil
1✔
245
}
246

247
func syncDaemonSet(ctx context.Context, client k8sclient.Client, scheme *runtime.Scheme, dc *sriovnetworkv1.SriovOperatorConfig, in *appsv1.DaemonSet) error {
1✔
248
        logger := log.Log.WithName("syncDaemonSet")
1✔
249
        logger.V(1).Info("Start to sync DaemonSet", "Namespace", in.Namespace, "Name", in.Name)
1✔
250
        var err error
1✔
251

1✔
252
        if err = controllerutil.SetControllerReference(dc, in, scheme); err != nil {
1✔
253
                return err
×
254
        }
×
255
        ds := &appsv1.DaemonSet{}
1✔
256
        err = client.Get(ctx, types.NamespacedName{Namespace: in.Namespace, Name: in.Name}, ds)
1✔
257
        if err != nil {
2✔
258
                if errors.IsNotFound(err) {
2✔
259
                        logger.V(1).Info("Created DaemonSet", in.Namespace, in.Name)
1✔
260
                        err = client.Create(ctx, in)
1✔
261
                        if err != nil {
1✔
262
                                logger.Error(err, "Fail to create Daemonset", "Namespace", in.Namespace, "Name", in.Name)
×
263
                                return err
×
264
                        }
×
265
                } else {
×
266
                        logger.Error(err, "Fail to get Daemonset", "Namespace", in.Namespace, "Name", in.Name)
×
267
                        return err
×
268
                }
×
269
        } else {
1✔
270
                logger.V(1).Info("DaemonSet already exists, updating")
1✔
271
                // DeepDerivative checks for changes only comparing non-zero fields in the source struct.
1✔
272
                // This skips default values added by the api server.
1✔
273
                // References in https://github.com/kubernetes-sigs/kubebuilder/issues/592#issuecomment-625738183
1✔
274

1✔
275
                // Note(Adrianc): we check Equality of OwnerReference as we changed sriov-device-plugin owner ref
1✔
276
                // from SriovNetworkNodePolicy to SriovOperatorConfig, hence even if there is no change in spec,
1✔
277
                // we need to update the obj's owner reference.
1✔
278

1✔
279
                if equality.Semantic.DeepEqual(in.OwnerReferences, ds.OwnerReferences) &&
1✔
280
                        equality.Semantic.DeepDerivative(in.Spec, ds.Spec) {
2✔
281
                        logger.V(1).Info("Daemonset spec did not change, not updating")
1✔
282
                        return nil
1✔
283
                }
1✔
284
                err = client.Update(ctx, in)
1✔
285
                if err != nil {
1✔
286
                        logger.Error(err, "Fail to update DaemonSet", "Namespace", in.Namespace, "Name", in.Name)
×
287
                        return err
×
288
                }
×
289
        }
290
        return nil
1✔
291
}
292

293
func updateDaemonsetNodeSelector(obj *uns.Unstructured, nodeSelector map[string]string) error {
1✔
294
        if len(nodeSelector) == 0 {
2✔
295
                return nil
1✔
296
        }
1✔
297

298
        ds := &appsv1.DaemonSet{}
1✔
299
        scheme := kscheme.Scheme
1✔
300
        err := scheme.Convert(obj, ds, nil)
1✔
301
        if err != nil {
1✔
302
                return fmt.Errorf("failed to convert Unstructured [%s] to DaemonSet: %v", obj.GetName(), err)
×
303
        }
×
304

305
        ds.Spec.Template.Spec.NodeSelector = nodeSelector
1✔
306

1✔
307
        err = scheme.Convert(ds, obj, nil)
1✔
308
        if err != nil {
1✔
309
                return fmt.Errorf("failed to convert DaemonSet [%s] to Unstructured: %v", obj.GetName(), err)
×
310
        }
×
311
        return nil
1✔
312
}
313

314
func findNodePoolConfig(ctx context.Context, node *corev1.Node, c k8sclient.Client) (*sriovnetworkv1.SriovNetworkPoolConfig, []corev1.Node, error) {
1✔
315
        logger := log.FromContext(ctx)
1✔
316
        logger.Info("FindNodePoolConfig():")
1✔
317
        // get all the sriov network pool configs
1✔
318
        npcl := &sriovnetworkv1.SriovNetworkPoolConfigList{}
1✔
319
        err := c.List(ctx, npcl)
1✔
320
        if err != nil {
1✔
321
                logger.Error(err, "failed to list sriovNetworkPoolConfig")
×
322
                return nil, nil, err
×
323
        }
×
324

325
        selectedNpcl := []*sriovnetworkv1.SriovNetworkPoolConfig{}
1✔
326
        nodesInPools := map[string]interface{}{}
1✔
327

1✔
328
        for _, npc := range npcl.Items {
2✔
329
                // we skip hw offload objects
1✔
330
                if npc.Spec.OvsHardwareOffloadConfig.Name != "" {
2✔
331
                        continue
1✔
332
                }
333

334
                if npc.Spec.NodeSelector == nil {
1✔
UNCOV
335
                        npc.Spec.NodeSelector = &metav1.LabelSelector{}
×
UNCOV
336
                }
×
337

338
                selector, err := metav1.LabelSelectorAsSelector(npc.Spec.NodeSelector)
1✔
339
                if err != nil {
1✔
340
                        logger.Error(err, "failed to create label selector from nodeSelector", "nodeSelector", npc.Spec.NodeSelector)
×
341
                        return nil, nil, err
×
342
                }
×
343

344
                if selector.Matches(labels.Set(node.Labels)) {
2✔
345
                        selectedNpcl = append(selectedNpcl, npc.DeepCopy())
1✔
346
                }
1✔
347

348
                nodeList := &corev1.NodeList{}
1✔
349
                err = c.List(ctx, nodeList, &k8sclient.ListOptions{LabelSelector: selector})
1✔
350
                if err != nil {
1✔
351
                        logger.Error(err, "failed to list all the nodes matching the pool with label selector from nodeSelector",
×
352
                                "machineConfigPoolName", npc,
×
353
                                "nodeSelector", npc.Spec.NodeSelector)
×
354
                        return nil, nil, err
×
355
                }
×
356

357
                for _, nodeName := range nodeList.Items {
2✔
358
                        nodesInPools[nodeName.Name] = nil
1✔
359
                }
1✔
360
        }
361

362
        if len(selectedNpcl) > 1 {
1✔
363
                // don't allow the node to be part of multiple pools
×
364
                err = fmt.Errorf("node is part of more then one pool")
×
365
                logger.Error(err, "multiple pools founded for a specific node", "numberOfPools", len(selectedNpcl), "pools", selectedNpcl)
×
366
                return nil, nil, err
×
367
        } else if len(selectedNpcl) == 1 {
2✔
368
                // found one pool for our node
1✔
369
                logger.V(2).Info("found sriovNetworkPool", "pool", *selectedNpcl[0])
1✔
370
                selector, err := metav1.LabelSelectorAsSelector(selectedNpcl[0].Spec.NodeSelector)
1✔
371
                if err != nil {
1✔
372
                        logger.Error(err, "failed to create label selector from nodeSelector", "nodeSelector", selectedNpcl[0].Spec.NodeSelector)
×
373
                        return nil, nil, err
×
374
                }
×
375

376
                // list all the nodes that are also part of this pool and return them
377
                nodeList := &corev1.NodeList{}
1✔
378
                err = c.List(ctx, nodeList, &k8sclient.ListOptions{LabelSelector: selector})
1✔
379
                if err != nil {
1✔
380
                        logger.Error(err, "failed to list nodes using with label selector", "labelSelector", selector)
×
381
                        return nil, nil, err
×
382
                }
×
383

384
                return selectedNpcl[0], nodeList.Items, nil
1✔
385
        } else {
1✔
386
                // in this case we get all the nodes and remove the ones that already part of any pool
1✔
387
                logger.V(1).Info("node doesn't belong to any pool, using default drain configuration with MaxUnavailable of one", "pool", *defaultPoolConfig)
1✔
388
                nodeList := &corev1.NodeList{}
1✔
389
                err = c.List(ctx, nodeList)
1✔
390
                if err != nil {
1✔
391
                        logger.Error(err, "failed to list all the nodes")
×
392
                        return nil, nil, err
×
393
                }
×
394

395
                defaultNodeLists := []corev1.Node{}
1✔
396
                for _, nodeObj := range nodeList.Items {
2✔
397
                        if _, exist := nodesInPools[nodeObj.Name]; !exist {
2✔
398
                                defaultNodeLists = append(defaultNodeLists, nodeObj)
1✔
399
                        }
1✔
400
                }
401
                return defaultPoolConfig, defaultNodeLists, nil
1✔
402
        }
403
}
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