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

k8snetworkplumbingwg / sriov-network-operator / 11775513002

11 Nov 2024 09:12AM UTC coverage: 47.024% (+1.4%) from 45.603%
11775513002

Pull #747

github

web-flow
Merge baa41c97a into 92fee7bec
Pull Request #747: Redesign device plugin reset

86 of 118 new or added lines in 4 files covered. (72.88%)

9 existing lines in 2 files now uncovered.

7103 of 15105 relevant lines covered (47.02%)

0.52 hits per line

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

69.96
/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
        if oldAnno != newAnno {
2✔
104
                return true
1✔
105
        }
1✔
106

107
        return false
1✔
108
}
109

110
type DrainStateAnnotationPredicate struct {
111
        predicate.Funcs
112
}
113

114
func (DrainStateAnnotationPredicate) Create(e event.CreateEvent) bool {
1✔
115
        if e.Object == nil {
1✔
116
                return false
×
117
        }
×
118

119
        if _, hasAnno := e.Object.GetLabels()[constants.NodeStateDrainAnnotationCurrent]; hasAnno {
2✔
120
                return true
1✔
121
        }
1✔
122
        return false
×
123
}
124

125
func (DrainStateAnnotationPredicate) Update(e event.UpdateEvent) bool {
1✔
126
        if e.ObjectOld == nil {
1✔
127
                return false
×
128
        }
×
129
        if e.ObjectNew == nil {
1✔
130
                return false
×
131
        }
×
132

133
        oldAnno, hasOldAnno := e.ObjectOld.GetLabels()[constants.NodeStateDrainAnnotationCurrent]
1✔
134
        newAnno, hasNewAnno := e.ObjectNew.GetLabels()[constants.NodeStateDrainAnnotationCurrent]
1✔
135

1✔
136
        if !hasOldAnno || !hasNewAnno {
1✔
137
                return true
×
138
        }
×
139

140
        if oldAnno != newAnno {
1✔
141
                return true
×
142
        }
×
143

144
        return oldAnno != newAnno
1✔
145
}
146

147
func GetImagePullSecrets() []string {
1✔
148
        imagePullSecrets := os.Getenv("IMAGE_PULL_SECRETS")
1✔
149
        if imagePullSecrets != "" {
1✔
150
                return strings.Split(imagePullSecrets, ",")
×
151
        } else {
1✔
152
                return []string{}
1✔
153
        }
1✔
154
}
155

156
func formatJSON(str string) (string, error) {
1✔
157
        var prettyJSON bytes.Buffer
1✔
158
        if err := json.Indent(&prettyJSON, []byte(str), "", "    "); err != nil {
1✔
159
                return "", err
×
160
        }
×
161
        return prettyJSON.String(), nil
1✔
162
}
163

164
// GetDefaultNodeSelector return a nodeSelector with worker and linux os
165
func GetDefaultNodeSelector() map[string]string {
1✔
166
        return map[string]string{
1✔
167
                "node-role.kubernetes.io/worker": "",
1✔
168
                "kubernetes.io/os":               "linux",
1✔
169
        }
1✔
170
}
1✔
171

172
// GetDefaultNodeSelectorForDevicePlugin return a nodeSelector with worker linux os
173
// and the enabled sriov device plugin
174
func GetNodeSelectorForDevicePlugin(dc *sriovnetworkv1.SriovOperatorConfig) map[string]string {
1✔
175
        if len(dc.Spec.ConfigDaemonNodeSelector) == 0 {
2✔
176
                return map[string]string{
1✔
177
                        "kubernetes.io/os":               "linux",
1✔
178
                        constants.SriovDevicePluginLabel: constants.SriovDevicePluginLabelEnabled,
1✔
179
                }
1✔
180
        }
1✔
181

182
        tmp := dc.Spec.DeepCopy()
1✔
183
        tmp.ConfigDaemonNodeSelector[constants.SriovDevicePluginLabel] = constants.SriovDevicePluginLabelEnabled
1✔
184
        return tmp.ConfigDaemonNodeSelector
1✔
185
}
186

187
func syncPluginDaemonObjs(ctx context.Context,
188
        client k8sclient.Client,
189
        scheme *runtime.Scheme,
190
        dc *sriovnetworkv1.SriovOperatorConfig) error {
1✔
191
        logger := log.Log.WithName("syncPluginDaemonObjs")
1✔
192
        logger.V(1).Info("Start to sync sriov daemons objects")
1✔
193

1✔
194
        // render plugin manifests
1✔
195
        data := render.MakeRenderData()
1✔
196
        data.Data["Namespace"] = vars.Namespace
1✔
197
        data.Data["SRIOVDevicePluginImage"] = os.Getenv("SRIOV_DEVICE_PLUGIN_IMAGE")
1✔
198
        data.Data["ReleaseVersion"] = os.Getenv("RELEASEVERSION")
1✔
199
        data.Data["ResourcePrefix"] = vars.ResourcePrefix
1✔
200
        data.Data["ImagePullSecrets"] = GetImagePullSecrets()
1✔
201
        data.Data["NodeSelectorField"] = GetNodeSelectorForDevicePlugin(dc)
1✔
202
        data.Data["UseCDI"] = dc.Spec.UseCDI
1✔
203
        objs, err := renderDsForCR(constants.PluginPath, &data)
1✔
204
        if err != nil {
1✔
205
                logger.Error(err, "Fail to render SR-IoV manifests")
×
206
                return err
×
207
        }
×
208

209
        // Sync DaemonSets
210
        for _, obj := range objs {
2✔
211
                err = syncDsObject(ctx, client, scheme, dc, obj)
1✔
212
                if err != nil {
1✔
213
                        logger.Error(err, "Couldn't sync SR-IoV daemons objects")
×
214
                        return err
×
215
                }
×
216
        }
217

218
        return nil
1✔
219
}
220

221
func syncDsObject(ctx context.Context, client k8sclient.Client, scheme *runtime.Scheme, dc *sriovnetworkv1.SriovOperatorConfig, obj *uns.Unstructured) error {
1✔
222
        logger := log.Log.WithName("syncDsObject")
1✔
223
        kind := obj.GetKind()
1✔
224
        logger.V(1).Info("Start to sync Objects", "Kind", kind)
1✔
225
        switch kind {
1✔
226
        case constants.ServiceAccount, constants.Role, constants.RoleBinding:
1✔
227
                if err := controllerutil.SetControllerReference(dc, obj, scheme); err != nil {
1✔
228
                        return err
×
229
                }
×
230
                if err := apply.ApplyObject(ctx, client, obj); err != nil {
1✔
231
                        logger.Error(err, "Fail to sync", "Kind", kind)
×
232
                        return err
×
233
                }
×
234
        case constants.DaemonSet:
1✔
235
                ds := &appsv1.DaemonSet{}
1✔
236
                err := scheme.Convert(obj, ds, nil)
1✔
237
                if err != nil {
1✔
238
                        logger.Error(err, "Fail to convert to DaemonSet")
×
239
                        return err
×
240
                }
×
241
                err = syncDaemonSet(ctx, client, scheme, dc, ds)
1✔
242
                if err != nil {
1✔
243
                        logger.Error(err, "Fail to sync DaemonSet", "Namespace", ds.Namespace, "Name", ds.Name)
×
244
                        return err
×
245
                }
×
246
        }
247
        return nil
1✔
248
}
249

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

1✔
255
        objs, err := render.RenderDir(path, data)
1✔
256
        if err != nil {
1✔
257
                return nil, errs.Wrap(err, "failed to render SR-IOV Network Operator manifests")
×
258
        }
×
259
        return objs, nil
1✔
260
}
261

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

1✔
267
        if err = controllerutil.SetControllerReference(dc, in, scheme); err != nil {
1✔
UNCOV
268
                return err
×
269
        }
×
270
        ds := &appsv1.DaemonSet{}
1✔
271
        err = client.Get(ctx, types.NamespacedName{Namespace: in.Namespace, Name: in.Name}, ds)
1✔
272
        if err != nil {
2✔
273
                if errors.IsNotFound(err) {
2✔
274
                        logger.V(1).Info("Created DaemonSet", in.Namespace, in.Name)
1✔
275
                        err = client.Create(ctx, in)
1✔
276
                        if err != nil {
1✔
277
                                logger.Error(err, "Fail to create Daemonset", "Namespace", in.Namespace, "Name", in.Name)
×
278
                                return err
×
279
                        }
×
280
                } else {
×
281
                        logger.Error(err, "Fail to get Daemonset", "Namespace", in.Namespace, "Name", in.Name)
×
282
                        return err
×
283
                }
×
284
        } else {
1✔
285
                logger.V(1).Info("DaemonSet already exists, updating")
1✔
286
                // DeepDerivative checks for changes only comparing non-zero fields in the source struct.
1✔
287
                // This skips default values added by the api server.
1✔
288
                // References in https://github.com/kubernetes-sigs/kubebuilder/issues/592#issuecomment-625738183
1✔
289

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

1✔
294
                if equality.Semantic.DeepEqual(in.OwnerReferences, ds.OwnerReferences) &&
1✔
295
                        equality.Semantic.DeepDerivative(in.Spec, ds.Spec) {
2✔
296
                        logger.V(1).Info("Daemonset spec did not change, not updating")
1✔
297
                        return nil
1✔
298
                }
1✔
299
                err = client.Update(ctx, in)
1✔
300
                if err != nil {
1✔
301
                        logger.Error(err, "Fail to update DaemonSet", "Namespace", in.Namespace, "Name", in.Name)
×
302
                        return err
×
303
                }
×
304
        }
305
        return nil
1✔
306
}
307

308
func updateDaemonsetNodeSelector(obj *uns.Unstructured, nodeSelector map[string]string) error {
1✔
309
        if len(nodeSelector) == 0 {
2✔
310
                return nil
1✔
311
        }
1✔
312

313
        ds := &appsv1.DaemonSet{}
1✔
314
        scheme := kscheme.Scheme
1✔
315
        err := scheme.Convert(obj, ds, nil)
1✔
316
        if err != nil {
1✔
317
                return fmt.Errorf("failed to convert Unstructured [%s] to DaemonSet: %v", obj.GetName(), err)
×
318
        }
×
319

320
        ds.Spec.Template.Spec.NodeSelector = nodeSelector
1✔
321

1✔
322
        err = scheme.Convert(ds, obj, nil)
1✔
323
        if err != nil {
1✔
324
                return fmt.Errorf("failed to convert DaemonSet [%s] to Unstructured: %v", obj.GetName(), err)
×
325
        }
×
326
        return nil
1✔
327
}
328

329
func findNodePoolConfig(ctx context.Context, node *corev1.Node, c k8sclient.Client) (*sriovnetworkv1.SriovNetworkPoolConfig, []corev1.Node, error) {
1✔
330
        logger := log.FromContext(ctx)
1✔
331
        logger.Info("FindNodePoolConfig():")
1✔
332
        // get all the sriov network pool configs
1✔
333
        npcl := &sriovnetworkv1.SriovNetworkPoolConfigList{}
1✔
334
        err := c.List(ctx, npcl)
1✔
335
        if err != nil {
1✔
336
                logger.Error(err, "failed to list sriovNetworkPoolConfig")
×
337
                return nil, nil, err
×
338
        }
×
339

340
        selectedNpcl := []*sriovnetworkv1.SriovNetworkPoolConfig{}
1✔
341
        nodesInPools := map[string]interface{}{}
1✔
342

1✔
343
        for _, npc := range npcl.Items {
2✔
344
                // we skip hw offload objects
1✔
345
                if npc.Spec.OvsHardwareOffloadConfig.Name != "" {
2✔
346
                        continue
1✔
347
                }
348

349
                if npc.Spec.NodeSelector == nil {
2✔
350
                        npc.Spec.NodeSelector = &metav1.LabelSelector{}
1✔
351
                }
1✔
352

353
                selector, err := metav1.LabelSelectorAsSelector(npc.Spec.NodeSelector)
1✔
354
                if err != nil {
1✔
355
                        logger.Error(err, "failed to create label selector from nodeSelector", "nodeSelector", npc.Spec.NodeSelector)
×
356
                        return nil, nil, err
×
357
                }
×
358

359
                if selector.Matches(labels.Set(node.Labels)) {
2✔
360
                        selectedNpcl = append(selectedNpcl, npc.DeepCopy())
1✔
361
                }
1✔
362

363
                nodeList := &corev1.NodeList{}
1✔
364
                err = c.List(ctx, nodeList, &k8sclient.ListOptions{LabelSelector: selector})
1✔
365
                if err != nil {
1✔
366
                        logger.Error(err, "failed to list all the nodes matching the pool with label selector from nodeSelector",
×
367
                                "machineConfigPoolName", npc,
×
368
                                "nodeSelector", npc.Spec.NodeSelector)
×
369
                        return nil, nil, err
×
370
                }
×
371

372
                for _, nodeName := range nodeList.Items {
2✔
373
                        nodesInPools[nodeName.Name] = nil
1✔
374
                }
1✔
375
        }
376

377
        if len(selectedNpcl) > 1 {
1✔
378
                // don't allow the node to be part of multiple pools
×
379
                err = fmt.Errorf("node is part of more then one pool")
×
380
                logger.Error(err, "multiple pools founded for a specific node", "numberOfPools", len(selectedNpcl), "pools", selectedNpcl)
×
381
                return nil, nil, err
×
382
        } else if len(selectedNpcl) == 1 {
2✔
383
                // found one pool for our node
1✔
384
                logger.V(2).Info("found sriovNetworkPool", "pool", *selectedNpcl[0])
1✔
385
                selector, err := metav1.LabelSelectorAsSelector(selectedNpcl[0].Spec.NodeSelector)
1✔
386
                if err != nil {
1✔
387
                        logger.Error(err, "failed to create label selector from nodeSelector", "nodeSelector", selectedNpcl[0].Spec.NodeSelector)
×
388
                        return nil, nil, err
×
389
                }
×
390

391
                // list all the nodes that are also part of this pool and return them
392
                nodeList := &corev1.NodeList{}
1✔
393
                err = c.List(ctx, nodeList, &k8sclient.ListOptions{LabelSelector: selector})
1✔
394
                if err != nil {
1✔
395
                        logger.Error(err, "failed to list nodes using with label selector", "labelSelector", selector)
×
396
                        return nil, nil, err
×
397
                }
×
398

399
                return selectedNpcl[0], nodeList.Items, nil
1✔
400
        } else {
1✔
401
                // in this case we get all the nodes and remove the ones that already part of any pool
1✔
402
                logger.V(1).Info("node doesn't belong to any pool, using default drain configuration with MaxUnavailable of one", "pool", *defaultPoolConfig)
1✔
403
                nodeList := &corev1.NodeList{}
1✔
404
                err = c.List(ctx, nodeList)
1✔
405
                if err != nil {
1✔
406
                        logger.Error(err, "failed to list all the nodes")
×
407
                        return nil, nil, err
×
408
                }
×
409

410
                defaultNodeLists := []corev1.Node{}
1✔
411
                for _, nodeObj := range nodeList.Items {
2✔
412
                        if _, exist := nodesInPools[nodeObj.Name]; !exist {
2✔
413
                                defaultNodeLists = append(defaultNodeLists, nodeObj)
1✔
414
                        }
1✔
415
                }
416
                return defaultPoolConfig, defaultNodeLists, nil
1✔
417
        }
418
}
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