/
kernel.go
142 lines (115 loc) · 4.93 KB
/
kernel.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
package kernel
import (
"fmt"
"strings"
"github.com/openshift/special-resource-operator/pkg/utils"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"sigs.k8s.io/controller-runtime/pkg/client"
)
//go:generate mockgen -source=kernel.go -package=kernel -destination=mock_kernel_api.go
type KernelData interface {
SetAffineAttributes(obj *unstructured.Unstructured, kernelFullVersion, operatingSystemMajorMinor string) error
IsObjectAffine(obj client.Object) bool
FullVersion(*corev1.NodeList) (string, error)
PatchVersion(kernelFullVersion string) (string, error)
}
type kernelData struct{}
func NewKernelData() KernelData {
return &kernelData{}
}
func (k *kernelData) SetAffineAttributes(obj *unstructured.Unstructured,
kernelFullVersion string,
operatingSystemMajorMinor string) error {
kernelVersion := strings.ReplaceAll(kernelFullVersion, "_", "-")
hash64, err := utils.FNV64a(operatingSystemMajorMinor + "-" + kernelVersion)
if err != nil {
return fmt.Errorf("could not get hash: %w", err)
}
name := obj.GetName() + "-" + hash64
obj.SetName(name)
switch obj.GetKind() {
case "BuildRun":
if err := unstructured.SetNestedField(obj.Object, name, "spec", "buildRef", "name"); err != nil {
return fmt.Errorf("could not set spec.buildRef.name in BuildRun object: %w", err)
}
case "DaemonSet", "Deployment", "StatefulSet":
if err := unstructured.SetNestedField(obj.Object, name, "metadata", "labels", "app"); err != nil {
return fmt.Errorf("could not set metadata.labels.app in DaemonSet object: %w", err)
}
if err := unstructured.SetNestedField(obj.Object, name, "spec", "selector", "matchLabels", "app"); err != nil {
return fmt.Errorf("could not set spec.selector.matchLabels.app in DaemonSet object: %w", err)
}
if err := unstructured.SetNestedField(obj.Object, name, "spec", "template", "metadata", "labels", "app"); err != nil {
return fmt.Errorf("could not set spec.template.metadata.labels.app in DaemonSet object: %w", err)
}
}
if err := k.setVersionNodeAffinity(obj, kernelFullVersion); err != nil {
return fmt.Errorf("cannot set kernel version node affinity for obj %s: %w", obj.GetKind(), err)
}
return nil
}
func (k *kernelData) setVersionNodeAffinity(obj *unstructured.Unstructured, kernelFullVersion string) error {
switch obj.GetKind() {
case "DaemonSet", "Deployment", "Statefulset":
if err := k.versionNodeAffinity(kernelFullVersion, obj, "spec", "template", "spec", "nodeSelector"); err != nil {
return fmt.Errorf("cannot setup %s's kernel version affinity: %w", obj.GetKind(), err)
}
case "Pod":
if err := k.versionNodeAffinity(kernelFullVersion, obj, "spec", "nodeSelector"); err != nil {
return fmt.Errorf("cannot setup %s's kernel version affinity: %w", obj.GetKind(), err)
}
case "BuildConfig":
if err := k.versionNodeAffinity(kernelFullVersion, obj, "spec", "nodeSelector"); err != nil {
return fmt.Errorf("cannot setup %s's kernel version affinity: %w", obj.GetKind(), err)
}
}
return nil
}
func (k *kernelData) versionNodeAffinity(kernelFullVersion string, obj *unstructured.Unstructured, fields ...string) error {
nodeSelector, found, err := unstructured.NestedMap(obj.Object, fields...)
if err != nil {
return fmt.Errorf("couldn't find %s in %s: %w", strings.Join(fields, "."), obj.GetKind(), err)
}
if !found {
nodeSelector = make(map[string]interface{})
}
nodeSelector["feature.node.kubernetes.io/kernel-version.full"] = kernelFullVersion
if err := unstructured.SetNestedMap(obj.Object, nodeSelector, fields...); err != nil {
return fmt.Errorf("couldn't set %s in %s: %w", strings.Join(fields, "."), obj.GetKind(), err)
}
return nil
}
func (k *kernelData) IsObjectAffine(obj client.Object) bool {
annotations := obj.GetAnnotations()
affine, found := annotations["specialresource.openshift.io/kernel-affine"]
return found && affine == "true"
}
func (k *kernelData) FullVersion(nodeList *corev1.NodeList) (string, error) {
var kernelFullVersion string
// Assuming all nodes are running the same kernel version,
// one could easily add driver-kernel-versions for each node.
for _, node := range nodeList.Items {
kernelFullVersion = node.Status.NodeInfo.KernelVersion
if len(kernelFullVersion) == 0 {
return "", fmt.Errorf("kernel not found for node %s", node.Name)
}
}
return kernelFullVersion, nil
}
// Using w.xx.y-zzz and looking at the fourth file listed /boot/vmlinuz-4.4.0-45 we can say:
// w = Kernel Version = 4
// xx= Major Revision = 4
// y = Minor Revision = 0
// zzz=Patch number = 45
func (k *kernelData) PatchVersion(kernelFullVersion string) (string, error) {
version := strings.Split(kernelFullVersion, "-")
// Happens only if kernel full version has no patch version sep by "-"
if len(version) == 1 {
short := strings.Split(kernelFullVersion, ".")
return short[0] + "." + short[1] + "." + short[2], nil
}
patch := strings.Split(version[1], ".")
// version.major.minor-patch
return version[0] + "-" + patch[0], nil
}